动态更新

当数据源发生更改时,FlexGrid会自动更新所有单元格。

如果您的数据源中只有少数项目经常更改,则仅更新绑定到实际更改的项目的单元格可能更有效。

下面的网格使用 formatItem 事件来跟踪每个数据项的单元格元素。 当数据发生变化时,它仅更新受影响的单元而不是整个网格。

import 'bootstrap.css'; import '@grapecity/wijmo.styles/wijmo.css'; import './styles.css'; import * as wjInput from '@grapecity/wijmo.input'; import * as wjGrid from '@grapecity/wijmo.grid'; import * as wjGridFilter from '@grapecity/wijmo.grid.filter'; import * as wjCore from '@grapecity/wijmo'; // document.readyState === 'complete' ? init() : window.onload = init; // function init() { // // Companies in the Financial Times Stock Exchange 100 Index // https://en.wikipedia.org/wiki/FTSE_100_Index // mcap: market capitalization (in billion pounds) // emp: employees (thousands) var companies = [ { symbol: 'RDSA', name: 'Royal Dutch Shell', sector: 'Oil and gas', mcap: 160.12, emp: 90 }, { symbol: 'ULVR', name: 'Unilever', sector: 'Consumer goods', mcap: 90.42, emp: 171 }, { symbol: 'HSBA', name: 'HSBC', sector: 'Banking', mcap: 88.11, emp: 267 }, { symbol: 'BATS', name: 'British American Tobacco', sector: 'Tobacco', mcap: 71.4, emp: 87 }, { symbol: 'GSK', name: 'GlaxoSmithKline', sector: 'Pharmaceuticals', mcap: 67.38, emp: 97 }, { symbol: 'SAB', name: 'SABMiller', sector: 'Beverages', mcap: 67.32, emp: 70 }, { symbol: 'BP', name: 'BP', sector: 'Oil and gas', mcap: 63.13, emp: 97 }, { symbol: 'VOD', name: 'Vodafone Group', sector: 'Telecomms', mcap: 56.55, emp: 86 }, { symbol: 'AZN', name: 'AstraZeneca', sector: 'Pharmaceuticals', mcap: 51.23, emp: 57 }, { symbol: 'RB', name: 'Reckitt Benckiser', sector: 'Consumer goods', mcap: 46.32, emp: 32 }, { symbol: 'DGE', name: 'Diageo', sector: 'Beverages', mcap: 46.01, emp: 25 }, { symbol: 'BT.A', name: 'BT Group', sector: 'Telecomms', mcap: 45.61, emp: 89 }, { symbol: 'LLOY', name: 'Lloyds Banking Group', sector: 'Banking', mcap: 44.11, emp: 120 }, { symbol: 'BLT', name: 'BHP Billiton', sector: 'Mining', mcap: 41.88, emp: 46 }, { symbol: 'NG', name: 'National Grid plc', sector: 'Energy', mcap: 36.14, emp: 27 }, { symbol: 'IMB', name: 'Imperial Brands', sector: 'Tobacco', mcap: 35.78, emp: 38 }, { symbol: 'RIO', name: 'Rio Tinto Group', sector: 'Mining', mcap: 34.84, emp: 67 }, { symbol: 'PRU', name: 'Prudential plc', sector: 'Finance', mcap: 31.63, emp: 25 }, { symbol: 'RBS', name: 'Royal Bank of Scotland Group', sector: 'Banking', mcap: 28.6, emp: 150 }, { symbol: 'BARC', name: 'Barclays', sector: 'Banking', mcap: 27.18, emp: 150 }, { symbol: 'ABF', name: 'Associated British Foods', sector: 'Food', mcap: 25.77, emp: 102 }, { symbol: 'REL', name: 'RELX Group', sector: 'Publishing', mcap: 25.54, emp: 28 }, { symbol: 'REX', name: 'Rexam', sector: 'Packaging', mcap: 25.54, emp: 19 }, { symbol: 'CCL', name: 'Carnival Corporation & plc', sector: 'Leisure', mcap: 24.85, emp: 86 }, { symbol: 'SHP', name: 'Shire plc', sector: 'Pharmaceuticals', mcap: 22.52, emp: 4 }, { symbol: 'CPG', name: 'Compass Group', sector: 'Food', mcap: 20.21, emp: 471 }, { symbol: 'WPP', name: 'WPP plc', sector: 'Media', mcap: 19.01, emp: 162 }, { symbol: 'AV.', name: 'Aviva', sector: 'Insurance', mcap: 17.69, emp: 40 }, { symbol: 'SKY', name: 'Sky plc', sector: 'Media', mcap: 17.5, emp: 22 }, { symbol: 'GLEN', name: 'Glencore', sector: 'Mining', mcap: 16.96, emp: 57 }, { symbol: 'BA.', name: 'BAE Systems', sector: 'Military', mcap: 16.01, emp: 107 }, { symbol: 'TSCO', name: 'Tesco', sector: 'Supermarket', mcap: 14.92, emp: 519 }, { symbol: 'SSE', name: 'SSE plc', sector: 'Energy', mcap: 14.03, emp: 19 }, { symbol: 'STAN', name: 'Standard Chartered', sector: 'Banking', mcap: 13.52, emp: 86 }, { symbol: 'LGEN', name: 'Legal & General', sector: 'Insurance', mcap: 13.21, emp: 9 }, { symbol: 'ARM', name: 'ARM Holdings', sector: 'Engineering', mcap: 13.2, emp: 2 }, { symbol: 'RR.', name: 'Rolls-Royce Holdings', sector: 'Manufacturing', mcap: 11.8, emp: 55 }, { symbol: 'EXPN', name: 'Experian', sector: 'Information', mcap: 11.1, emp: 17 }, { symbol: 'IAG', name: 'International Consolidated Airlines Group SA', sector: 'Travel', mcap: 11.01, emp: 58 }, { symbol: 'CRH', name: 'CRH plc', sector: 'Building materials', mcap: 10.9, emp: 76 }, { symbol: 'CNA', name: 'Centrica', sector: 'Energy', mcap: 10.72, emp: 40 }, { symbol: 'SN.', name: 'Smith & Nephew', sector: 'Medical', mcap: 10.27, emp: 11 }, { symbol: 'ITV', name: 'ITV plc', sector: 'Media', mcap: 10.15, emp: 4 }, { symbol: 'WOS', name: 'Wolseley plc', sector: 'Building materials', mcap: 9.2, emp: 44 }, { symbol: 'OML', name: 'Old Mutual', sector: 'Insurance', mcap: 8.45, emp: 54 }, { symbol: 'LAND', name: 'Land Securities', sector: 'Property', mcap: 8.19, emp: 0 }, { symbol: 'LSE', name: 'London Stock Exchange Group', sector: 'Finance', mcap: 8.06, emp: 4 }, { symbol: 'KGF', name: 'Kingfisher plc', sector: 'Retail homeware', mcap: 7.8, emp: 80 }, { symbol: 'CPI', name: 'Capita', sector: 'Support Services', mcap: 7.38, emp: 46 }, { symbol: 'BLND', name: 'British Land', sector: 'Property', mcap: 7.13, emp: 0 }, { symbol: 'WTB', name: 'Whitbread', sector: 'Retail hospitality', mcap: 7.09, emp: 86 }, { symbol: 'MKS', name: 'Marks & Spencer', sector: 'Retailer', mcap: 7.01, emp: 81 }, { symbol: 'FRES', name: 'Fresnillo plc', sector: 'Mining', mcap: 6.99, emp: 2 }, { symbol: 'NXT', name: 'Next plc', sector: 'Retail clothing', mcap: 6.9, emp: 58 }, { symbol: 'SDR', name: 'Schroders', sector: 'Fund management', mcap: 6.63, emp: 3 }, { symbol: 'SL', name: 'Standard Life', sector: 'Fund management', mcap: 6.63, emp: 10 }, { symbol: 'PSON', name: 'Pearson PLC', sector: 'Education', mcap: 6.52, emp: 37 }, { symbol: 'BNZL', name: 'Bunzl', sector: 'Industrial products', mcap: 6.38, emp: 12 }, { symbol: 'MNDI', name: 'Mondi', sector: 'Manufacturing', mcap: 6.37, emp: 26 }, { symbol: 'UU', name: 'United Utilities', sector: 'Water', mcap: 6.36, emp: 5 }, { symbol: 'PSN', name: 'Persimmon plc', sector: 'Building', mcap: 6.34, emp: 2 }, { symbol: 'SGE', name: 'Sage Group', sector: 'IT', mcap: 6.26, emp: 12 }, { symbol: 'EZJ', name: 'EasyJet', sector: 'Travel', mcap: 6.17, emp: 11 }, { symbol: 'AAL', name: 'Anglo American plc', sector: 'Mining', mcap: 6.09, emp: 100 }, { symbol: 'TW.', name: 'Taylor Wimpey', sector: 'Building', mcap: 5.99, emp: 3 }, { symbol: 'TUI', name: 'TUI Group', sector: 'Leisure', mcap: 5.99, emp: 76 }, { symbol: 'WPG', name: 'Worldpay', sector: 'Payment services', mcap: 5.9, emp: 4 }, { symbol: 'RRS', name: 'Randgold Resources', sector: 'Mining', mcap: 5.89, emp: 6 }, { symbol: 'HL', name: 'Hargreaves Lansdown', sector: 'Finance', mcap: 5.87, emp: 0 }, { symbol: 'BDEV', name: 'Barratt Developments', sector: 'Building', mcap: 5.86, emp: 5 }, { symbol: 'IHG', name: 'InterContinental Hotels Group', sector: 'Hotels', mcap: 5.75, emp: 345 }, { symbol: 'BRBY', name: 'Burberry', sector: 'Fashion', mcap: 5.65, emp: 10 }, { symbol: 'DC.', name: 'Dixons Carphone', sector: 'Retail', mcap: 5.16, emp: 40 }, { symbol: 'DLG', name: 'Direct Line Group', sector: 'Insurance', mcap: 5.15, emp: 13 }, { symbol: 'CCH', name: 'Coca-Cola HBC AG', sector: 'Consumer', mcap: 5.1, emp: 38 }, { symbol: 'SVT', name: 'Severn Trent', sector: 'Water', mcap: 5.04, emp: 8 }, { symbol: 'DCC', name: 'DCC plc', sector: 'Investments', mcap: 5.03, emp: 9 }, { symbol: 'SBRY', name: 'Sainsbury\'s', sector: 'Supermarket', mcap: 5.02, emp: 150 }, { symbol: 'ADM', name: 'Admiral Group', sector: 'Insurance', mcap: 4.91, emp: 2 }, { symbol: 'GKN', name: 'GKN', sector: 'Manufacturing', mcap: 4.79, emp: 50 }, { symbol: 'JMAT', name: 'Johnson Matthey', sector: 'Chemicals', mcap: 4.79, emp: 9 }, { symbol: 'PFG', name: 'Provident Financial', sector: 'Finance', mcap: 4.74, emp: 3 }, { symbol: 'ANTO', name: 'Antofagasta', sector: 'Mining', mcap: 4.71, emp: 4 }, { symbol: 'STJ', name: 'St. James\'s Place plc', sector: 'Finance', mcap: 4.68, emp: 1 }, { symbol: 'ITRK', name: 'Intertek', sector: 'Product testing', mcap: 4.67, emp: 33 }, { symbol: 'BAB', name: 'Babcock International', sector: 'Engineering', mcap: 4.65, emp: 34 }, { symbol: 'BKG', name: 'Berkeley Group Holdings', sector: 'Building', mcap: 4.6, emp: 2 }, { symbol: 'ISAT', name: 'Inmarsat', sector: 'Telecomms', mcap: 4.47, emp: 1 }, { symbol: 'TPK', name: 'Travis Perkins', sector: 'Retailer', mcap: 4.46, emp: 24 }, { symbol: 'HMSO', name: 'Hammerson', sector: 'Property', mcap: 4.42, emp: 0 }, { symbol: 'MERL', name: 'Merlin Entertainments', sector: 'Leisure', mcap: 4.42, emp: 28 }, { symbol: 'RMG', name: 'Royal Mail', sector: 'Delivery', mcap: 4.41, emp: 150 }, { symbol: 'AHT', name: 'Ashtead Group', sector: 'Equipment rental', mcap: 4.26, emp: 12 }, { symbol: 'RSA', name: 'RSA Insurance Group', sector: 'Insurance', mcap: 4.16, emp: 21 }, { symbol: 'III', name: '3i', sector: 'Private equity', mcap: 4.06, emp: 0 }, { symbol: 'INTU', name: 'Intu Properties', sector: 'Property', mcap: 3.89, emp: 2 }, { symbol: 'SMIN', name: 'Smiths Group', sector: 'Engineering', mcap: 3.84, emp: 23 }, { symbol: 'HIK', name: 'Hikma Pharmaceuticals', sector: 'Manufacturing', mcap: 3.71, emp: 6 }, { symbol: 'ADN', name: 'Aberdeen Asset Management', sector: 'Fund management', mcap: 3.14, emp: 1 }, { symbol: 'SPD', name: 'Sports Direct', sector: 'Retail', mcap: 2.4, emp: 17 } ]; // // Trading Market Data // https://en.wikipedia.org/wiki/Market_data var data = [], now = new Date(); companies.forEach(function (company) { var bid = randBetween(1, 100000) / 100, ask = bid + randBetween(0, 100) / 100; data.push({ symbol: company.symbol, name: company.name, bid: bid, ask: ask, lastSale: bid, bidSize: randBetween(10, 500), askSize: randBetween(10, 500), lastSize: randBetween(10, 500), volume: randBetween(10000, 20000), quoteTime: now, tradeTime: now, askHistory: [ask, ask], bidHistory: [bid, bid], saleHistory: [bid, bid] }); }); // control panel var customCells = true; var autoUpdate = true; var interval = 100; // update interval in ms: 1000, 500, 100, 10, 1 var batchSize = 5; // items to update: 100, 50, 10, 5, 1 document.getElementById('customCells').addEventListener('click', function (e) { customCells = e.target.checked; theGrid.invalidate(); }); document.getElementById('autoUpdate').addEventListener('click', function (e) { autoUpdate = e.target.checked; theGrid.invalidate(); }); var cmbInterval = new wjInput.ComboBox('#updateInterval', { itemsSource: [1000, 500, 100, 10, 1], selectedValue: interval, selectedIndexChanged: function (s, e) { interval = cmbInterval.selectedValue; } }); var cmbBatchSize = new wjInput.ComboBox('#batchSize', { itemsSource: [100, 50, 10, 5, 1], selectedValue: batchSize, selectedIndexChanged: function (s, e) { batchSize = cmbBatchSize.selectedValue; } }); // // create and bind the grid var theGrid = new wjGrid.FlexGrid('#theGrid', { isReadOnly: true, selectionMode: 'Row', autoGenerateColumns: false, columns: [ { binding: 'name', header: 'Name', width: 200 }, { binding: 'bid', header: 'Bid', format: 'n2', width: 200 }, { binding: 'ask', header: 'Ask', format: 'n2', width: 200 }, { binding: 'lastSale', header: 'Last Sale', format: 'n2', width: 200 }, { binding: 'bidSize', header: 'Bid Size', format: 'n0' }, { binding: 'askSize', header: 'Ask Size', format: 'n0' }, { binding: 'lastSize', header: 'Last Size', format: 'n0' }, { binding: 'volume', header: 'Volume', format: 'n0' }, { binding: 'quoteTime', header: 'Quote Time', format: 'hh:mm:ss', align: 'center' }, { binding: 'tradeTime', header: 'Trade Time', format: 'hh:mm:ss', align: 'center' }, ], itemsSource: data }); theGrid.rowHeaders.columns[0].width = 80; // // add a filter var f = new wjGridFilter.FlexGridFilter(theGrid); // // cellElements object keeps track of grid's cell elements var clearCells = false; var cellElements = {}; theGrid.updatingView.addHandler(function (s, e) { clearCells = true; // clear cell elements on next formatItem }); // // formatItem hander displays cells and keeps track of cell elements theGrid.formatItem.addHandler(function (s, e) { // // show symbols in row headers if (e.panel == s.rowHeaders && e.col == 0) { e.cell.textContent = item = s.rows[e.row].dataItem.symbol; } // // regular cells if (e.panel == s.cells) { var col = s.columns[e.col], item = s.rows[e.row].dataItem; // // clear cell elements if (clearCells) { clearCells = false; cellElements = {}; } // // store cell element if (!cellElements[item.symbol]) { cellElements[item.symbol] = { item: item }; } cellElements[item.symbol][col.binding] = e.cell; // // custom painting formatCell(e.cell, item, col, false); } }); // // custom cell painting function formatCell(cell, item, col, flare) { if (customCells) { switch (col.binding) { case 'bid': formatDynamicCell(cell, item, col, 'bidHistory', flare); break; case 'ask': formatDynamicCell(cell, item, col, 'askHistory', flare); break; case 'lastSale': formatDynamicCell(cell, item, col, 'saleHistory', flare); break; default: cell.textContent = wjCore.Globalize.format(item[col.binding], col.format); break; } } else { cell.textContent = wjCore.Globalize.format(item[col.binding], col.format); } } function formatDynamicCell(cell, item, col, history, flare) { // // cell template var html = '<div class="ticker chg-{dir} flare-{fdir}"> ' + '<div class="value">{value}</div >' + '<div class="chg">{chg}</div>' + '<div class="glyph"><span class="wj-glyph-{glyph}"></span></div>' + '<div class="spark">{spark}</div>' + '</div>'; // // value html = html.replace('{value}', wjCore.Globalize.format(item[col.binding], col.format)); // // % change var hist = item[history]; var chg = hist.length > 1 ? hist[hist.length - 1] / hist[hist.length - 2] - 1 : 0; html = html.replace('{chg}', wjCore.Globalize.format(chg * 100, 'n1') + '%'); // // up/down glyph var glyph = chg > +0.001 ? 'up' : chg < -0.001 ? 'down' : 'circle'; html = html.replace('{glyph}', glyph); // // sparklines html = html.replace('{spark}', getSparklines(item[history])); // // change direction var dir = glyph == 'circle' ? 'none' : glyph; html = html.replace('{dir}', dir); // // flare direction var flareDir = flare ? dir : 'none'; html = html.replace('{fdir}', flareDir); // // done cell.innerHTML = html; } // // update grid cells when items change function updateGrid(changedItems) { for (var symbol in changedItems) { var itemCells = cellElements[symbol]; if (itemCells) { var item = itemCells.item; theGrid.columns.forEach(function (col) { var cell = itemCells[col.binding]; if (cell) { formatCell(cell, item, col, true); } }); } } } // // simulate updates/notifications updateTrades(); function updateTrades() { var now = new Date(); var changedItems = {}; for (var i = 0; i < batchSize; i++) { // // select an item var item = data[randBetween(0, data.length - 1)]; // // update current data item.bid = item.bid * (1 + (Math.random() * .11 - .05)); item.ask = item.ask * (1 + (Math.random() * .11 - .05)); item.bidSize = randBetween(10, 1000); item.askSize = randBetween(10, 1000); var sale = (item.ask + item.bid) / 2; item.lastSale = sale; item.lastSize = Math.floor((item.askSize + item.bidSize) / 2); item.quoteTime = now; item.tradeTime = new Date(Date.now() + randBetween(0, 60000)); // // update history data addHistory(item.askHistory, item.ask); addHistory(item.bidHistory, item.bid); addHistory(item.saleHistory, item.lastSale); // // keep track of changed items changedItems[item.symbol] = true; } // // update the grid if (autoUpdate) { updateGrid(changedItems); } // // and schedule the next batch setTimeout(updateTrades, interval); } } ; // // add a value to a history array function addHistory(array, data) { array.push(data); if (array.length > 10) { // limit history length array.splice(0, 1); } } // // get a random number within a given interval function randBetween(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); } // // generate sparklines as SVG function getSparklines(data) { var svg = '', min = Math.min.apply(Math, data), max = Math.max.apply(Math, data), x1 = 0, y1 = scaleY(data[0], min, max); for (var i = 1; i < data.length; i++) { var x2 = Math.round((i) / (data.length - 1) * 100); var y2 = scaleY(data[i], min, max); svg += '<line x1=' + x1 + '% y1=' + y1 + '% x2=' + x2 + '% y2=' + y2 + '% />'; x1 = x2; y1 = y2; } return '<svg><g>' + svg + '</g></svg>'; } function scaleY(value, min, max) { return max > min ? 100 - Math.round((value - min) / (max - min) * 100) : 0; } <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>GrapeCity Wijmo FlexGrid Dynamic Updates</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"> <label> Custom Cells <input id="customCells" type="checkbox" checked="checked"> </label> <label> Auto Update <input id="autoUpdate" type="checkbox" checked="checked"> </label> <label> Update Interval (ms) <input id="updateInterval"> </label> <label> Batch Size (# items) <input id="batchSize"> </label> <div id="theGrid"></div> </div> </body> </html> label { font-weight: normal; margin-right: 20px; } body { margin-bottom: 40px; } /* Wijmo Controls */ .wj-combobox { width: 100px; } .wj-flexgrid { max-height: 500px; } .wj-flexgrid .wj-cell { padding: 6px; } .wj-flexgrid .wj-cell:not(.wj-header) { border-bottom: none; } /* ticker cell */ .ticker div { display: inline-block; } .ticker .chg { font-size: 75%; opacity: .75; text-align: center; width: 4em; } .ticker .glyph { font-size: 120%; text-align: center; width: 1em; } .ticker .spark { padding: 0 4px; width: 4em; height: 1em; opacity: .65; } .ticker .spark svg { width: 100%; height: 100%; stroke: currentColor; stroke-width: 2px; overflow: visible; } /* value going up */ .wj-cell:not(.wj-state-selected):not(.wj-state-multi-selected) .ticker.chg-up .chg, .wj-cell:not(.wj-state-selected):not(.wj-state-multi-selected) .ticker.chg-up .glyph { color: green; } /* value going down */ .wj-cell:not(.wj-state-selected):not(.wj-state-multi-selected) .ticker.chg-down .chg, .wj-cell:not(.wj-state-selected):not(.wj-state-multi-selected) .ticker.chg-down .glyph { color: red; } /* value not changing */ .ticker.chg-none .chg, .ticker.chg-none .glyph { opacity: .25; } /* up/down 'flare' animations */ .ticker.flare-up:after { content: ''; position: absolute; left: 0; top: 0; width: 100%; height: 100%; animation: 1s ease-out flare-up; } @keyframes flare-up { from { background: rgba(50, 255, 50, 0.5); } } .ticker.flare-down:after { content: ''; position: absolute; left: 0; top: 0; width: 100%; height: 100%; animation: 1s ease-out flare-down; } @keyframes flare-down { from { background: rgba(255, 50, 50, 0.5); } } import 'bootstrap.css'; import '@grapecity/wijmo.styles/wijmo.css'; import './styles.css'; // import { Component, enableProdMode, NgModule, ViewChild } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import * as wjcCore from '@grapecity/wijmo'; import * as wjcInput from '@grapecity/wijmo.input'; import * as wjcGrid from '@grapecity/wijmo.grid'; import { WjInputModule } from '@grapecity/wijmo.angular2.input'; import { WjGridModule } from '@grapecity/wijmo.angular2.grid'; import { WjGridFilterModule } from '@grapecity/wijmo.angular2.grid.filter'; // @Component({ selector: 'app-component', templateUrl: 'src/app.component.html' }) export class AppComponent { data: any[]; customCells: boolean = true; autoUpdate: boolean = true; interval: number = 100; // update interval in ms: 1000, 500, 100, 10, 1 intervalItems = [1000, 500, 100, 10, 1] batchSize: number = 5; // items to update: 100, 50, 10, 5, 1 batchSizeItems = [100, 50, 10, 5, 1]; private _clearCells: boolean = false; private _cellElements: any = {}; // DataSvc will be passed by derived classes constructor() { this.data = this._getData(200); } @ViewChild('flex') flex: wjcGrid.FlexGrid; initializeGrid(flex: wjcGrid.FlexGrid) { flex.rowHeaders.columns[0].width = 80; flex.updatingView.addHandler(() => { this._clearCells = true; // clear cell elements on next formatItem }); flex.formatItem.addHandler((s: wjcGrid.FlexGrid, e: wjcGrid.FormatItemEventArgs) => { // // show symbols in row headers if (e.panel == s.rowHeaders && e.col == 0) { e.cell.textContent = item = s.rows[e.row].dataItem.symbol; } // // regular cells if (e.panel == s.cells) { var col = s.columns[e.col], item = s.rows[e.row].dataItem; // // clear cell elements if (this._clearCells) { this._clearCells = false; this._cellElements = {}; } // // store cell element if (!this._cellElements[item.symbol]) { this._cellElements[item.symbol] = { item: item }; } this._cellElements[item.symbol][col.binding] = e.cell; // // custom painting this._formatCell(e.cell, item, col, false); } }); this._updateTrades(); } invalidateGrid() { this.flex.invalidate(); } private _getData(cnt: number) { // Companies in the Financial Times Stock Exchange 100 Index // https://en.wikipedia.org/wiki/FTSE_100_Index // mcap: market capitalization (in billion pounds) // emp: employees (thousands) let companies = [ { symbol: 'RDSA', name: 'Royal Dutch Shell', sector: 'Oil and gas', mcap: 160.12, emp: 90 }, { symbol: 'ULVR', name: 'Unilever', sector: 'Consumer goods', mcap: 90.42, emp: 171 }, { symbol: 'HSBA', name: 'HSBC', sector: 'Banking', mcap: 88.11, emp: 267 }, { symbol: 'BATS', name: 'British American Tobacco', sector: 'Tobacco', mcap: 71.4, emp: 87 }, { symbol: 'GSK', name: 'GlaxoSmithKline', sector: 'Pharmaceuticals', mcap: 67.38, emp: 97 }, { symbol: 'SAB', name: 'SABMiller', sector: 'Beverages', mcap: 67.32, emp: 70 }, { symbol: 'BP', name: 'BP', sector: 'Oil and gas', mcap: 63.13, emp: 97 }, { symbol: 'VOD', name: 'Vodafone Group', sector: 'Telecomms', mcap: 56.55, emp: 86 }, { symbol: 'AZN', name: 'AstraZeneca', sector: 'Pharmaceuticals', mcap: 51.23, emp: 57 }, { symbol: 'RB', name: 'Reckitt Benckiser', sector: 'Consumer goods', mcap: 46.32, emp: 32 }, { symbol: 'DGE', name: 'Diageo', sector: 'Beverages', mcap: 46.01, emp: 25 }, { symbol: 'BT.A', name: 'BT Group', sector: 'Telecomms', mcap: 45.61, emp: 89 }, { symbol: 'LLOY', name: 'Lloyds Banking Group', sector: 'Banking', mcap: 44.11, emp: 120 }, { symbol: 'BLT', name: 'BHP Billiton', sector: 'Mining', mcap: 41.88, emp: 46 }, { symbol: 'NG', name: 'National Grid plc', sector: 'Energy', mcap: 36.14, emp: 27 }, { symbol: 'IMB', name: 'Imperial Brands', sector: 'Tobacco', mcap: 35.78, emp: 38 }, { symbol: 'RIO', name: 'Rio Tinto Group', sector: 'Mining', mcap: 34.84, emp: 67 }, { symbol: 'PRU', name: 'Prudential plc', sector: 'Finance', mcap: 31.63, emp: 25 }, { symbol: 'RBS', name: 'Royal Bank of Scotland Group', sector: 'Banking', mcap: 28.6, emp: 150 }, { symbol: 'BARC', name: 'Barclays', sector: 'Banking', mcap: 27.18, emp: 150 }, { symbol: 'ABF', name: 'Associated British Foods', sector: 'Food', mcap: 25.77, emp: 102 }, { symbol: 'REL', name: 'RELX Group', sector: 'Publishing', mcap: 25.54, emp: 28 }, { symbol: 'REX', name: 'Rexam', sector: 'Packaging', mcap: 25.54, emp: 19 }, { symbol: 'CCL', name: 'Carnival Corporation & plc', sector: 'Leisure', mcap: 24.85, emp: 86 }, { symbol: 'SHP', name: 'Shire plc', sector: 'Pharmaceuticals', mcap: 22.52, emp: 4 }, { symbol: 'CPG', name: 'Compass Group', sector: 'Food', mcap: 20.21, emp: 471 }, { symbol: 'WPP', name: 'WPP plc', sector: 'Media', mcap: 19.01, emp: 162 }, { symbol: 'AV.', name: 'Aviva', sector: 'Insurance', mcap: 17.69, emp: 40 }, { symbol: 'SKY', name: 'Sky plc', sector: 'Media', mcap: 17.5, emp: 22 }, { symbol: 'GLEN', name: 'Glencore', sector: 'Mining', mcap: 16.96, emp: 57 }, { symbol: 'BA.', name: 'BAE Systems', sector: 'Military', mcap: 16.01, emp: 107 }, { symbol: 'TSCO', name: 'Tesco', sector: 'Supermarket', mcap: 14.92, emp: 519 }, { symbol: 'SSE', name: 'SSE plc', sector: 'Energy', mcap: 14.03, emp: 19 }, { symbol: 'STAN', name: 'Standard Chartered', sector: 'Banking', mcap: 13.52, emp: 86 }, { symbol: 'LGEN', name: 'Legal & General', sector: 'Insurance', mcap: 13.21, emp: 9 }, { symbol: 'ARM', name: 'ARM Holdings', sector: 'Engineering', mcap: 13.2, emp: 2 }, { symbol: 'RR.', name: 'Rolls-Royce Holdings', sector: 'Manufacturing', mcap: 11.8, emp: 55 }, { symbol: 'EXPN', name: 'Experian', sector: 'Information', mcap: 11.1, emp: 17 }, { symbol: 'IAG', name: 'International Consolidated Airlines Group SA', sector: 'Travel', mcap: 11.01, emp: 58 }, { symbol: 'CRH', name: 'CRH plc', sector: 'Building materials', mcap: 10.9, emp: 76 }, { symbol: 'CNA', name: 'Centrica', sector: 'Energy', mcap: 10.72, emp: 40 }, { symbol: 'SN.', name: 'Smith & Nephew', sector: 'Medical', mcap: 10.27, emp: 11 }, { symbol: 'ITV', name: 'ITV plc', sector: 'Media', mcap: 10.15, emp: 4 }, { symbol: 'WOS', name: 'Wolseley plc', sector: 'Building materials', mcap: 9.2, emp: 44 }, { symbol: 'OML', name: 'Old Mutual', sector: 'Insurance', mcap: 8.45, emp: 54 }, { symbol: 'LAND', name: 'Land Securities', sector: 'Property', mcap: 8.19, emp: 0 }, { symbol: 'LSE', name: 'London Stock Exchange Group', sector: 'Finance', mcap: 8.06, emp: 4 }, { symbol: 'KGF', name: 'Kingfisher plc', sector: 'Retail homeware', mcap: 7.8, emp: 80 }, { symbol: 'CPI', name: 'Capita', sector: 'Support Services', mcap: 7.38, emp: 46 }, { symbol: 'BLND', name: 'British Land', sector: 'Property', mcap: 7.13, emp: 0 }, { symbol: 'WTB', name: 'Whitbread', sector: 'Retail hospitality', mcap: 7.09, emp: 86 }, { symbol: 'MKS', name: 'Marks & Spencer', sector: 'Retailer', mcap: 7.01, emp: 81 }, { symbol: 'FRES', name: 'Fresnillo plc', sector: 'Mining', mcap: 6.99, emp: 2 }, { symbol: 'NXT', name: 'Next plc', sector: 'Retail clothing', mcap: 6.9, emp: 58 }, { symbol: 'SDR', name: 'Schroders', sector: 'Fund management', mcap: 6.63, emp: 3 }, { symbol: 'SL', name: 'Standard Life', sector: 'Fund management', mcap: 6.63, emp: 10 }, { symbol: 'PSON', name: 'Pearson PLC', sector: 'Education', mcap: 6.52, emp: 37 }, { symbol: 'BNZL', name: 'Bunzl', sector: 'Industrial products', mcap: 6.38, emp: 12 }, { symbol: 'MNDI', name: 'Mondi', sector: 'Manufacturing', mcap: 6.37, emp: 26 }, { symbol: 'UU', name: 'United Utilities', sector: 'Water', mcap: 6.36, emp: 5 }, { symbol: 'PSN', name: 'Persimmon plc', sector: 'Building', mcap: 6.34, emp: 2 }, { symbol: 'SGE', name: 'Sage Group', sector: 'IT', mcap: 6.26, emp: 12 }, { symbol: 'EZJ', name: 'EasyJet', sector: 'Travel', mcap: 6.17, emp: 11 }, { symbol: 'AAL', name: 'Anglo American plc', sector: 'Mining', mcap: 6.09, emp: 100 }, { symbol: 'TW.', name: 'Taylor Wimpey', sector: 'Building', mcap: 5.99, emp: 3 }, { symbol: 'TUI', name: 'TUI Group', sector: 'Leisure', mcap: 5.99, emp: 76 }, { symbol: 'WPG', name: 'Worldpay', sector: 'Payment services', mcap: 5.9, emp: 4 }, { symbol: 'RRS', name: 'Randgold Resources', sector: 'Mining', mcap: 5.89, emp: 6 }, { symbol: 'HL', name: 'Hargreaves Lansdown', sector: 'Finance', mcap: 5.87, emp: 0 }, { symbol: 'BDEV', name: 'Barratt Developments', sector: 'Building', mcap: 5.86, emp: 5 }, { symbol: 'IHG', name: 'InterContinental Hotels Group', sector: 'Hotels', mcap: 5.75, emp: 345 }, { symbol: 'BRBY', name: 'Burberry', sector: 'Fashion', mcap: 5.65, emp: 10 }, { symbol: 'DC.', name: 'Dixons Carphone', sector: 'Retail', mcap: 5.16, emp: 40 }, { symbol: 'DLG', name: 'Direct Line Group', sector: 'Insurance', mcap: 5.15, emp: 13 }, { symbol: 'CCH', name: 'Coca-Cola HBC AG', sector: 'Consumer', mcap: 5.1, emp: 38 }, { symbol: 'SVT', name: 'Severn Trent', sector: 'Water', mcap: 5.04, emp: 8 }, { symbol: 'DCC', name: 'DCC plc', sector: 'Investments', mcap: 5.03, emp: 9 }, { symbol: 'SBRY', name: 'Sainsbury\'s', sector: 'Supermarket', mcap: 5.02, emp: 150 }, { symbol: 'ADM', name: 'Admiral Group', sector: 'Insurance', mcap: 4.91, emp: 2 }, { symbol: 'GKN', name: 'GKN', sector: 'Manufacturing', mcap: 4.79, emp: 50 }, { symbol: 'JMAT', name: 'Johnson Matthey', sector: 'Chemicals', mcap: 4.79, emp: 9 }, { symbol: 'PFG', name: 'Provident Financial', sector: 'Finance', mcap: 4.74, emp: 3 }, { symbol: 'ANTO', name: 'Antofagasta', sector: 'Mining', mcap: 4.71, emp: 4 }, { symbol: 'STJ', name: 'St. James\'s Place plc', sector: 'Finance', mcap: 4.68, emp: 1 }, { symbol: 'ITRK', name: 'Intertek', sector: 'Product testing', mcap: 4.67, emp: 33 }, { symbol: 'BAB', name: 'Babcock International', sector: 'Engineering', mcap: 4.65, emp: 34 }, { symbol: 'BKG', name: 'Berkeley Group Holdings', sector: 'Building', mcap: 4.6, emp: 2 }, { symbol: 'ISAT', name: 'Inmarsat', sector: 'Telecomms', mcap: 4.47, emp: 1 }, { symbol: 'TPK', name: 'Travis Perkins', sector: 'Retailer', mcap: 4.46, emp: 24 }, { symbol: 'HMSO', name: 'Hammerson', sector: 'Property', mcap: 4.42, emp: 0 }, { symbol: 'MERL', name: 'Merlin Entertainments', sector: 'Leisure', mcap: 4.42, emp: 28 }, { symbol: 'RMG', name: 'Royal Mail', sector: 'Delivery', mcap: 4.41, emp: 150 }, { symbol: 'AHT', name: 'Ashtead Group', sector: 'Equipment rental', mcap: 4.26, emp: 12 }, { symbol: 'RSA', name: 'RSA Insurance Group', sector: 'Insurance', mcap: 4.16, emp: 21 }, { symbol: 'III', name: '3i', sector: 'Private equity', mcap: 4.06, emp: 0 }, { symbol: 'INTU', name: 'Intu Properties', sector: 'Property', mcap: 3.89, emp: 2 }, { symbol: 'SMIN', name: 'Smiths Group', sector: 'Engineering', mcap: 3.84, emp: 23 }, { symbol: 'HIK', name: 'Hikma Pharmaceuticals', sector: 'Manufacturing', mcap: 3.71, emp: 6 }, { symbol: 'ADN', name: 'Aberdeen Asset Management', sector: 'Fund management', mcap: 3.14, emp: 1 }, { symbol: 'SPD', name: 'Sports Direct', sector: 'Retail', mcap: 2.4, emp: 17 } ]; // Trading Market Data // https://en.wikipedia.org/wiki/Market_data let data: any[] = [], now = new Date(); companies.forEach((company: any) => { let bid = this._randBetween(1, 100000) / 100, ask = bid + this._randBetween(0, 100) / 100; data.push({ symbol: company.symbol, name: company.name, bid: bid, ask: ask, lastSale: bid, bidSize: this._randBetween(10, 500), askSize: this._randBetween(10, 500), lastSize: this._randBetween(10, 500), volume: this._randBetween(10000, 20000), quoteTime: now, tradeTime: now, askHistory: [ask, ask], bidHistory: [bid, bid], saleHistory: [bid, bid] }); }); // done return data; } // get a random number within a given interval private _randBetween(min: number, max: number) { return Math.floor(Math.random() * (max - min + 1) + min); } // custom cell painting private _formatCell(cell: HTMLElement, item: any, col: wjcGrid.Column, flare: boolean) { if (this.customCells) { switch (col.binding) { case 'bid': this._formatDynamicCell(cell, item, col, 'bidHistory', flare); break; case 'ask': this._formatDynamicCell(cell, item, col, 'askHistory', flare); break; case 'lastSale': this._formatDynamicCell(cell, item, col, 'saleHistory', flare); break; default: cell.textContent = wjcCore.Globalize.format(item[col.binding], col.format); break; } } else { cell.textContent = wjcCore.Globalize.format(item[col.binding], col.format); } } private _formatDynamicCell(cell: HTMLElement, item: any, col: wjcGrid.Column, history: string, flare: boolean) { // cell template let html = '<div class="ticker chg-{dir} flare-{fdir}"> ' + '<div class="value">{value}</div >' + '<div class="chg">{chg}</div>' + '<div class="glyph"><span class="wj-glyph-{glyph}"></span></div>' + '<div class="spark">{spark}</div>' + '</div>'; // value html = html.replace('{value}', wjcCore.Globalize.format(item[col.binding], col.format)); // % change let hist = item[history]; let chg = hist.length > 1 ? hist[hist.length - 1] / hist[hist.length - 2] - 1 : 0; html = html.replace('{chg}', wjcCore.Globalize.format(chg * 100, 'n1') + '%'); // up/down glyph let glyph = chg > +0.001 ? 'up' : chg < -0.001 ? 'down' : 'circle'; html = html.replace('{glyph}', glyph); // sparklines html = html.replace('{spark}', this._getSparklines(item[history])); // change direction let dir = glyph == 'circle' ? 'none' : glyph; html = html.replace('{dir}', dir); // flare direction let flareDir = flare ? dir : 'none'; html = html.replace('{fdir}', flareDir); // done cell.innerHTML = html; } // // update grid cells when items change private _updateGrid(changedItems: any) { for (let symbol in changedItems) { let itemCells = this._cellElements[symbol]; if (itemCells) { let item = itemCells.item; this.flex.columns.forEach((col: wjcGrid.Column) => { let cell = itemCells[col.binding]; if (cell) { this._formatCell(cell, item, col, true); } }) } } } private _updateTrades() { let now = new Date(); let changedItems = {}; for (let i = 0; i < this.batchSize; i++) { // select an item let item = this.data[this._randBetween(0, this.data.length - 1)]; // update current data item.bid = item.bid * (1 + (Math.random() * .11 - .05)); item.ask = item.ask * (1 + (Math.random() * .11 - .05)); item.bidSize = this._randBetween(10, 1000); item.askSize = this._randBetween(10, 1000); var sale = (item.ask + item.bid) / 2; item.lastSale = sale; item.lastSize = Math.floor((item.askSize + item.bidSize) / 2); item.quoteTime = now; item.tradeTime = new Date(Date.now() + this._randBetween(0, 60000)); // update history data this._addHistory(item.askHistory, item.ask); this._addHistory(item.bidHistory, item.bid); this._addHistory(item.saleHistory, item.lastSale); // // keep track of changed items changedItems[item.symbol] = true; } // // update the grid if (this.autoUpdate) { this._updateGrid(changedItems); } // // and schedule the next batch setTimeout(this._updateTrades.bind(this), this.interval); } // add a value to a history array private _addHistory(array: number[], data: number) { array.push(data); if (array.length > 10) { // limit history length array.splice(0, 1); } } // generate sparklines as SVG private _getSparklines(data: number[]) { let svg = '', 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); let y2 = this._scaleY(data[i], min, max); svg += '<line x1=' + x1 + '% y1=' + y1 + '% x2=' + x2 + '% y2=' + y2 + '% />'; x1 = x2; y1 = y2; } return '<svg><g>' + svg + '</g></svg>'; } private _scaleY(value: number, min: number, max: number) { return max > min ? 100 - Math.round((value - min) / (max - min) * 100) : 0; } } // @NgModule({ imports: [WjGridModule, WjInputModule, WjGridFilterModule, BrowserModule, FormsModule], declarations: [AppComponent], 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 FlexGrid Dynamic Updates</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"> <label> Custom Cells <input [(ngModel)]="customCells" (click)="invalidateGrid()" type="checkbox" /> </label> <label> Auto Update <input [(ngModel)]="autoUpdate" type="checkbox" /> </label> <label> Update Interval (ms) <wj-combo-box [itemsSource]="intervalItems" [(selectedValue)]="interval"></wj-combo-box> </label> <label> Batch Size (# items) <wj-combo-box [itemsSource]="batchSizeItems" [(selectedValue)]="batchSize"></wj-combo-box> </label> <wj-flex-grid #flex [isReadOnly]="true" [selectionMode]="'Row'" (initialized)="initializeGrid(flex)" [(itemsSource)]="data"> <wj-flex-grid-filter></wj-flex-grid-filter> <wj-flex-grid-column [binding]="'name'" [header]="'Name'" [width]="200"></wj-flex-grid-column> <wj-flex-grid-column [binding]="'bid'" [header]="'Bid'" [width]="200" [format]="'n2'"></wj-flex-grid-column> <wj-flex-grid-column [binding]="'ask'" [header]="'Ask'" [width]="200" [format]="'n2'"></wj-flex-grid-column> <wj-flex-grid-column [binding]="'lastSale'" [header]="'Last Sale'" [width]="200" [format]="'n2'"></wj-flex-grid-column> <wj-flex-grid-column [binding]="'bidSize'" [header]="'Bid Size'" [format]="'n0'"></wj-flex-grid-column> <wj-flex-grid-column [binding]="'askSize'" [header]="'Ask Size'" [format]="'n0'"></wj-flex-grid-column> <wj-flex-grid-column [binding]="'lastSize'" [header]="'Last Size'" [format]="'n0'"></wj-flex-grid-column> <wj-flex-grid-column [binding]="'volume'" [header]="'Volume'" [format]="'n0'"></wj-flex-grid-column> <wj-flex-grid-column [binding]="'quoteTime'" [header]="'Quote Time'" [format]="'hh:mm:ss'" [align]="'center'"></wj-flex-grid-column> <wj-flex-grid-column [binding]="'tradeTime'" [header]="'Trade Time'" [format]="'hh:mm:ss'" [align]="'center'"></wj-flex-grid-column> </wj-flex-grid> </div> label { font-weight: normal; margin-right: 20px; } body { margin-bottom: 40px; } /* Wijmo Controls */ .wj-combobox { width: 100px; } .wj-flexgrid { max-height: 500px; } .wj-flexgrid .wj-cell { padding: 6px; } .wj-flexgrid .wj-cell:not(.wj-header) { border-bottom: none; } /* ticker cell */ .ticker div { display: inline-block; } .ticker .chg { font-size: 75%; opacity: .75; text-align: center; width: 4em; } .ticker .glyph { font-size: 120%; text-align: center; width: 1em; } .ticker .spark { padding: 0 4px; width: 4em; height: 1em; opacity: .65; } .ticker .spark svg { width: 100%; height: 100%; stroke: currentColor; stroke-width: 2px; overflow: visible; } /* value going up */ .wj-cell:not(.wj-state-selected):not(.wj-state-multi-selected) .ticker.chg-up .chg, .wj-cell:not(.wj-state-selected):not(.wj-state-multi-selected) .ticker.chg-up .glyph { color: green; } /* value going down */ .wj-cell:not(.wj-state-selected):not(.wj-state-multi-selected) .ticker.chg-down .chg, .wj-cell:not(.wj-state-selected):not(.wj-state-multi-selected) .ticker.chg-down .glyph { color: red; } /* value not changing */ .ticker.chg-none .chg, .ticker.chg-none .glyph { opacity: .25; } /* up/down 'flare' animations */ .ticker.flare-up:after { content: ''; position: absolute; left: 0; top: 0; width: 100%; height: 100%; animation: 1s ease-out flare-up; } @keyframes flare-up { from { background: rgba(50, 255, 50, 0.5); } } .ticker.flare-down:after { content: ''; position: absolute; left: 0; top: 0; width: 100%; height: 100%; animation: 1s ease-out flare-down; } @keyframes flare-down { from { background: rgba(255, 50, 50, 0.5); } } <template> <div class="container-fluid"> <label> Custom Cells <input v-model="customCells" @click="invalidateGrid" type="checkbox"> </label> <label> Auto Update <input v-model="autoUpdate" type="checkbox"> </label> <label>Update Interval (ms) <wj-combo-box :itemsSource="intervalItems" :selectedValue="interval" :textChanged="intervalItemsChange"></wj-combo-box> </label> <label>Batch Size (# items) <wj-combo-box :itemsSource="batchSizeItems" :selectedValue="batchSize" :textChanged="batchSizeItemsChange"></wj-combo-box> </label> <wj-flex-grid :isReadOnly="true" :selectionMode="'Row'" :initialized="initializeGrid" :itemsSource="gridData" > <wj-flex-grid-filter></wj-flex-grid-filter> <wj-flex-grid-column :binding="'name'" :header="'Name'" :width="200"></wj-flex-grid-column> <wj-flex-grid-column :binding="'bid'" :header="'Bid'" :width="200" :format="'n2'"></wj-flex-grid-column> <wj-flex-grid-column :binding="'ask'" :header="'Ask'" :width="200" :format="'n2'"></wj-flex-grid-column> <wj-flex-grid-column :binding="'lastSale'" :header="'Last Sale'" :width="200" :format="'n2'" ></wj-flex-grid-column> <wj-flex-grid-column :binding="'bidSize'" :header="'Bid Size'" :format="'n0'"></wj-flex-grid-column> <wj-flex-grid-column :binding="'askSize'" :header="'Ask Size'" :format="'n0'"></wj-flex-grid-column> <wj-flex-grid-column :binding="'lastSize'" :header="'Last Size'" :format="'n0'"></wj-flex-grid-column> <wj-flex-grid-column :binding="'volume'" :header="'Volume'" :format="'n0'"></wj-flex-grid-column> <wj-flex-grid-column :binding="'quoteTime'" :header="'Quote Time'" :format="'hh:mm:ss'" :align="'center'" ></wj-flex-grid-column> <wj-flex-grid-column :binding="'tradeTime'" :header="'Trade Time'" :format="'hh:mm:ss'" :align="'center'" ></wj-flex-grid-column> </wj-flex-grid> </div> </template> <script> import "@grapecity/wijmo.styles/wijmo.css"; import "bootstrap.css"; import Vue from "vue"; import * as wjcCore from "@grapecity/wijmo"; import * as wjcGrid from "@grapecity/wijmo.grid"; import "@grapecity/wijmo.vue2.input"; import "@grapecity/wijmo.vue2.grid"; import "@grapecity/wijmo.vue2.grid.filter"; let App = Vue.extend({ name: "app", data: function() { return { cellElements: {}, gridData: [], customCells: true, autoUpdate: true, interval: 100, // update interval in ms: 1000, 500, 100, 10, 1 intervalItems: [1000, 500, 100, 10, 1], batchSize: 5, // items to update: 100, 50, 10, 5, 1 batchSizeItems: [100, 50, 10, 5, 1], clearCells: false, }; }, methods: { initializeGrid(flex) { this.flex = flex; flex.rowHeaders.columns[0].width = 80; flex.updatingView.addHandler(() => { this.clearCells = true; // clear cell elements on next formatItem }); flex.formatItem.addHandler((s, e) => { // // show symbols in row headers if (e.panel == s.rowHeaders && e.col == 0) { e.cell.textContent = item = s.rows[e.row].dataItem.symbol; } // // regular cells if (e.panel == s.cells) { var col = s.columns[e.col], item = s.rows[e.row].dataItem; // // clear cell elements if (this.clearCells) { this.clearCells = false; this.cellElements = {}; } // // store cell element if (!this.cellElements[item.symbol]) { this.cellElements[item.symbol] = { item: item }; } this.cellElements[item.symbol][col.binding] = e.cell; // // custom painting this._formatCell(e.cell, item, col, false); } }); this._updateTrades(); }, invalidateGrid: function() { this.flex.invalidate(); }, intervalItemsChange: function(s, e) { this.interval = s.selectedValue; }, batchSizeItemsChange: function(s, e) { this.batchSize = s.selectedValue; }, _getData: function(cnt) { // Companies in the Financial Times Stock Exchange 100 Index // https://en.wikipedia.org/wiki/FTSE_100_Index // mcap: market capitalization (in billion pounds) // emp: employees (thousands) let companies = [ { symbol: "RDSA", name: "Royal Dutch Shell", sector: "Oil and gas", mcap: 160.12, emp: 90 }, { symbol: "ULVR", name: "Unilever", sector: "Consumer goods", mcap: 90.42, emp: 171 }, { symbol: "HSBA", name: "HSBC", sector: "Banking", mcap: 88.11, emp: 267 }, { symbol: "BATS", name: "British American Tobacco", sector: "Tobacco", mcap: 71.4, emp: 87 }, { symbol: "GSK", name: "GlaxoSmithKline", sector: "Pharmaceuticals", mcap: 67.38, emp: 97 }, { symbol: "SAB", name: "SABMiller", sector: "Beverages", mcap: 67.32, emp: 70 }, { symbol: "BP", name: "BP", sector: "Oil and gas", mcap: 63.13, emp: 97 }, { symbol: "VOD", name: "Vodafone Group", sector: "Telecomms", mcap: 56.55, emp: 86 }, { symbol: "AZN", name: "AstraZeneca", sector: "Pharmaceuticals", mcap: 51.23, emp: 57 }, { symbol: "RB", name: "Reckitt Benckiser", sector: "Consumer goods", mcap: 46.32, emp: 32 }, { symbol: "DGE", name: "Diageo", sector: "Beverages", mcap: 46.01, emp: 25 }, { symbol: "BT.A", name: "BT Group", sector: "Telecomms", mcap: 45.61, emp: 89 }, { symbol: "LLOY", name: "Lloyds Banking Group", sector: "Banking", mcap: 44.11, emp: 120 }, { symbol: "BLT", name: "BHP Billiton", sector: "Mining", mcap: 41.88, emp: 46 }, { symbol: "NG", name: "National Grid plc", sector: "Energy", mcap: 36.14, emp: 27 }, { symbol: "IMB", name: "Imperial Brands", sector: "Tobacco", mcap: 35.78, emp: 38 }, { symbol: "RIO", name: "Rio Tinto Group", sector: "Mining", mcap: 34.84, emp: 67 }, { symbol: "PRU", name: "Prudential plc", sector: "Finance", mcap: 31.63, emp: 25 }, { symbol: "RBS", name: "Royal Bank of Scotland Group", sector: "Banking", mcap: 28.6, emp: 150 }, { symbol: "BARC", name: "Barclays", sector: "Banking", mcap: 27.18, emp: 150 }, { symbol: "ABF", name: "Associated British Foods", sector: "Food", mcap: 25.77, emp: 102 }, { symbol: "REL", name: "RELX Group", sector: "Publishing", mcap: 25.54, emp: 28 }, { symbol: "REX", name: "Rexam", sector: "Packaging", mcap: 25.54, emp: 19 }, { symbol: "CCL", name: "Carnival Corporation & plc", sector: "Leisure", mcap: 24.85, emp: 86 }, { symbol: "SHP", name: "Shire plc", sector: "Pharmaceuticals", mcap: 22.52, emp: 4 }, { symbol: "CPG", name: "Compass Group", sector: "Food", mcap: 20.21, emp: 471 }, { symbol: "WPP", name: "WPP plc", sector: "Media", mcap: 19.01, emp: 162 }, { symbol: "AV.", name: "Aviva", sector: "Insurance", mcap: 17.69, emp: 40 }, { symbol: "SKY", name: "Sky plc", sector: "Media", mcap: 17.5, emp: 22 }, { symbol: "GLEN", name: "Glencore", sector: "Mining", mcap: 16.96, emp: 57 }, { symbol: "BA.", name: "BAE Systems", sector: "Military", mcap: 16.01, emp: 107 }, { symbol: "TSCO", name: "Tesco", sector: "Supermarket", mcap: 14.92, emp: 519 }, { symbol: "SSE", name: "SSE plc", sector: "Energy", mcap: 14.03, emp: 19 }, { symbol: "STAN", name: "Standard Chartered", sector: "Banking", mcap: 13.52, emp: 86 }, { symbol: "LGEN", name: "Legal & General", sector: "Insurance", mcap: 13.21, emp: 9 }, { symbol: "ARM", name: "ARM Holdings", sector: "Engineering", mcap: 13.2, emp: 2 }, { symbol: "RR.", name: "Rolls-Royce Holdings", sector: "Manufacturing", mcap: 11.8, emp: 55 }, { symbol: "EXPN", name: "Experian", sector: "Information", mcap: 11.1, emp: 17 }, { symbol: "IAG", name: "International Consolidated Airlines Group SA", sector: "Travel", mcap: 11.01, emp: 58 }, { symbol: "CRH", name: "CRH plc", sector: "Building materials", mcap: 10.9, emp: 76 }, { symbol: "CNA", name: "Centrica", sector: "Energy", mcap: 10.72, emp: 40 }, { symbol: "SN.", name: "Smith & Nephew", sector: "Medical", mcap: 10.27, emp: 11 }, { symbol: "ITV", name: "ITV plc", sector: "Media", mcap: 10.15, emp: 4 }, { symbol: "WOS", name: "Wolseley plc", sector: "Building materials", mcap: 9.2, emp: 44 }, { symbol: "OML", name: "Old Mutual", sector: "Insurance", mcap: 8.45, emp: 54 }, { symbol: "LAND", name: "Land Securities", sector: "Property", mcap: 8.19, emp: 0 }, { symbol: "LSE", name: "London Stock Exchange Group", sector: "Finance", mcap: 8.06, emp: 4 }, { symbol: "KGF", name: "Kingfisher plc", sector: "Retail homeware", mcap: 7.8, emp: 80 }, { symbol: "CPI", name: "Capita", sector: "Support Services", mcap: 7.38, emp: 46 }, { symbol: "BLND", name: "British Land", sector: "Property", mcap: 7.13, emp: 0 }, { symbol: "WTB", name: "Whitbread", sector: "Retail hospitality", mcap: 7.09, emp: 86 }, { symbol: "MKS", name: "Marks & Spencer", sector: "Retailer", mcap: 7.01, emp: 81 }, { symbol: "FRES", name: "Fresnillo plc", sector: "Mining", mcap: 6.99, emp: 2 }, { symbol: "NXT", name: "Next plc", sector: "Retail clothing", mcap: 6.9, emp: 58 }, { symbol: "SDR", name: "Schroders", sector: "Fund management", mcap: 6.63, emp: 3 }, { symbol: "SL", name: "Standard Life", sector: "Fund management", mcap: 6.63, emp: 10 }, { symbol: "PSON", name: "Pearson PLC", sector: "Education", mcap: 6.52, emp: 37 }, { symbol: "BNZL", name: "Bunzl", sector: "Industrial products", mcap: 6.38, emp: 12 }, { symbol: "MNDI", name: "Mondi", sector: "Manufacturing", mcap: 6.37, emp: 26 }, { symbol: "UU", name: "United Utilities", sector: "Water", mcap: 6.36, emp: 5 }, { symbol: "PSN", name: "Persimmon plc", sector: "Building", mcap: 6.34, emp: 2 }, { symbol: "SGE", name: "Sage Group", sector: "IT", mcap: 6.26, emp: 12 }, { symbol: "EZJ", name: "EasyJet", sector: "Travel", mcap: 6.17, emp: 11 }, { symbol: "AAL", name: "Anglo American plc", sector: "Mining", mcap: 6.09, emp: 100 }, { symbol: "TW.", name: "Taylor Wimpey", sector: "Building", mcap: 5.99, emp: 3 }, { symbol: "TUI", name: "TUI Group", sector: "Leisure", mcap: 5.99, emp: 76 }, { symbol: "WPG", name: "Worldpay", sector: "Payment services", mcap: 5.9, emp: 4 }, { symbol: "RRS", name: "Randgold Resources", sector: "Mining", mcap: 5.89, emp: 6 }, { symbol: "HL", name: "Hargreaves Lansdown", sector: "Finance", mcap: 5.87, emp: 0 }, { symbol: "BDEV", name: "Barratt Developments", sector: "Building", mcap: 5.86, emp: 5 }, { symbol: "IHG", name: "InterContinental Hotels Group", sector: "Hotels", mcap: 5.75, emp: 345 }, { symbol: "BRBY", name: "Burberry", sector: "Fashion", mcap: 5.65, emp: 10 }, { symbol: "DC.", name: "Dixons Carphone", sector: "Retail", mcap: 5.16, emp: 40 }, { symbol: "DLG", name: "Direct Line Group", sector: "Insurance", mcap: 5.15, emp: 13 }, { symbol: "CCH", name: "Coca-Cola HBC AG", sector: "Consumer", mcap: 5.1, emp: 38 }, { symbol: "SVT", name: "Severn Trent", sector: "Water", mcap: 5.04, emp: 8 }, { symbol: "DCC", name: "DCC plc", sector: "Investments", mcap: 5.03, emp: 9 }, { symbol: "SBRY", name: "Sainsbury's", sector: "Supermarket", mcap: 5.02, emp: 150 }, { symbol: "ADM", name: "Admiral Group", sector: "Insurance", mcap: 4.91, emp: 2 }, { symbol: "GKN", name: "GKN", sector: "Manufacturing", mcap: 4.79, emp: 50 }, { symbol: "JMAT", name: "Johnson Matthey", sector: "Chemicals", mcap: 4.79, emp: 9 }, { symbol: "PFG", name: "Provident Financial", sector: "Finance", mcap: 4.74, emp: 3 }, { symbol: "ANTO", name: "Antofagasta", sector: "Mining", mcap: 4.71, emp: 4 }, { symbol: "STJ", name: "St. James's Place plc", sector: "Finance", mcap: 4.68, emp: 1 }, { symbol: "ITRK", name: "Intertek", sector: "Product testing", mcap: 4.67, emp: 33 }, { symbol: "BAB", name: "Babcock International", sector: "Engineering", mcap: 4.65, emp: 34 }, { symbol: "BKG", name: "Berkeley Group Holdings", sector: "Building", mcap: 4.6, emp: 2 }, { symbol: "ISAT", name: "Inmarsat", sector: "Telecomms", mcap: 4.47, emp: 1 }, { symbol: "TPK", name: "Travis Perkins", sector: "Retailer", mcap: 4.46, emp: 24 }, { symbol: "HMSO", name: "Hammerson", sector: "Property", mcap: 4.42, emp: 0 }, { symbol: "MERL", name: "Merlin Entertainments", sector: "Leisure", mcap: 4.42, emp: 28 }, { symbol: "RMG", name: "Royal Mail", sector: "Delivery", mcap: 4.41, emp: 150 }, { symbol: "AHT", name: "Ashtead Group", sector: "Equipment rental", mcap: 4.26, emp: 12 }, { symbol: "RSA", name: "RSA Insurance Group", sector: "Insurance", mcap: 4.16, emp: 21 }, { symbol: "III", name: "3i", sector: "Private equity", mcap: 4.06, emp: 0 }, { symbol: "INTU", name: "Intu Properties", sector: "Property", mcap: 3.89, emp: 2 }, { symbol: "SMIN", name: "Smiths Group", sector: "Engineering", mcap: 3.84, emp: 23 }, { symbol: "HIK", name: "Hikma Pharmaceuticals", sector: "Manufacturing", mcap: 3.71, emp: 6 }, { symbol: "ADN", name: "Aberdeen Asset Management", sector: "Fund management", mcap: 3.14, emp: 1 }, { symbol: "SPD", name: "Sports Direct", sector: "Retail", mcap: 2.4, emp: 17 } ]; // Trading Market Data // https://en.wikipedia.org/wiki/Market_data let data = [], now = new Date(); companies.forEach(company => { let bid = this._randBetween(1, 100000) / 100, ask = bid + this._randBetween(0, 100) / 100; data.push({ symbol: company.symbol, name: company.name, bid: bid, ask: ask, lastSale: bid, bidSize: this._randBetween(10, 500), askSize: this._randBetween(10, 500), lastSize: this._randBetween(10, 500), volume: this._randBetween(10000, 20000), quoteTime: now, tradeTime: now, askHistory: [ask, ask], bidHistory: [bid, bid], saleHistory: [bid, bid] }); }); // done return data; }, // get a random number within a given interval _randBetween: function(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); }, // custom cell painting _formatCell: function(cell, item, col, flare) { if (this.customCells) { switch (col.binding) { case "bid": this._formatDynamicCell( cell, item, col, "bidHistory", flare ); break; case "ask": this._formatDynamicCell( cell, item, col, "askHistory", flare ); break; case "lastSale": this._formatDynamicCell( cell, item, col, "saleHistory", flare ); break; default: cell.textContent = wjcCore.Globalize.format( item[col.binding], col.format ); break; } } else { cell.textContent = wjcCore.Globalize.format( item[col.binding], col.format ); } }, _formatDynamicCell: function(cell, item, col, history, flare) { // cell template let html = '<div class="ticker chg-{dir} flare-{fdir}"> ' + '<div class="value">{value}</div >' + '<div class="chg">{chg}</div>' + '<div class="glyph"><span class="wj-glyph-{glyph}"></span></div>' + '<div class="spark">{spark}</div>' + "</div>"; // value html = html.replace( "{value}", wjcCore.Globalize.format(item[col.binding], col.format) ); // % change let hist = item[history]; let chg = hist.length > 1 ? hist[hist.length - 1] / hist[hist.length - 2] - 1 : 0; html = html.replace( "{chg}", wjcCore.Globalize.format(chg * 100, "n1") + "%" ); // up/down glyph let glyph = chg > +0.001 ? "up" : chg < -0.001 ? "down" : "circle"; html = html.replace("{glyph}", glyph); // sparklines html = html.replace("{spark}", this._getSparklines(item[history])); // change direction let dir = glyph == "circle" ? "none" : glyph; html = html.replace("{dir}", dir); // flare direction let flareDir = flare ? dir : "none"; html = html.replace("{fdir}", flareDir); // done cell.innerHTML = html; }, // // update grid cells when items change _updateGrid: function(changedItems) { for (let symbol in changedItems) { let itemCells = this.cellElements[symbol]; if (itemCells) { let item = itemCells.item; this.flex.columns.forEach(col => { let cell = itemCells[col.binding]; if (cell) { this._formatCell(cell, item, col, true); } }); } } }, _updateTrades: function() { let now = new Date(); let changedItems = {}; for (let i = 0; i < this.batchSize; i++) { // select an item let item = this.gridData[ this._randBetween(0, this.gridData.length - 1) ]; // update current data item.bid = item.bid * (1 + (Math.random() * 0.11 - 0.05)); item.ask = item.ask * (1 + (Math.random() * 0.11 - 0.05)); item.bidSize = this._randBetween(10, 1000); item.askSize = this._randBetween(10, 1000); var sale = (item.ask + item.bid) / 2; item.lastSale = sale; item.lastSize = Math.floor((item.askSize + item.bidSize) / 2); item.quoteTime = now; item.tradeTime = new Date( Date.now() + this._randBetween(0, 60000) ); // update history data this._addHistory(item.askHistory, item.ask); this._addHistory(item.bidHistory, item.bid); this._addHistory(item.saleHistory, item.lastSale); // // keep track of changed items changedItems[item.symbol] = true; } // // update the grid if (this.autoUpdate) { this._updateGrid(changedItems); } // // and schedule the next batch setTimeout(this._updateTrades.bind(this), this.interval); }, // add a value to a history array _addHistory: function(array, data) { array.push(data); if (array.length > 10) { // limit history length array.splice(0, 1); } }, // generate sparklines as SVG _getSparklines: function(data) { let svg = "", 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); let y2 = this._scaleY(data[i], min, max); svg += "<line x1=" + x1 + "% y1=" + y1 + "% x2=" + x2 + "% y2=" + y2 + "% />"; x1 = x2; y1 = y2; } return "<svg><g>" + svg + "</g></svg>"; }, _scaleY: function(value, min, max) { return max > min ? 100 - Math.round(((value - min) / (max - min)) * 100) : 0; } }, created:function(){ this.gridData = this._getData(); } }); new Vue({ render: h => h(App) }).$mount("#app"); </script> <style> label { font-weight: normal; margin-right: 20px; } body { margin-bottom: 40px; } /* Wijmo Controls */ .wj-combobox { width: 100px; } .wj-flexgrid { max-height: 500px; } .wj-flexgrid .wj-cell { padding: 6px; } .wj-flexgrid .wj-cell:not(.wj-header) { border-bottom: none; } /* ticker cell */ .ticker div { display: inline-block; } .ticker .chg { font-size: 75%; opacity: 0.75; text-align: center; width: 4em; } .ticker .glyph { font-size: 120%; text-align: center; width: 1em; } .ticker .spark { padding: 0 4px; width: 4em; height: 1em; opacity: 0.65; } .ticker .spark svg { width: 100%; height: 100%; stroke: currentColor; stroke-width: 2px; overflow: visible; } /* value going up */ .wj-cell:not(.wj-state-selected):not(.wj-state-multi-selected) .ticker.chg-up .chg, .wj-cell:not(.wj-state-selected):not(.wj-state-multi-selected) .ticker.chg-up .glyph { color: green; } /* value going down */ .wj-cell:not(.wj-state-selected):not(.wj-state-multi-selected) .ticker.chg-down .chg, .wj-cell:not(.wj-state-selected):not(.wj-state-multi-selected) .ticker.chg-down .glyph { color: red; } /* value not changing */ .ticker.chg-none .chg, .ticker.chg-none .glyph { opacity: 0.25; } /* up/down 'flare' animations */ .ticker.flare-up:after { content: ""; position: absolute; left: 0; top: 0; width: 100%; height: 100%; animation: 1s ease-out flare-up; } @keyframes flare-up { from { background: rgba(50, 255, 50, 0.5); } } .ticker.flare-down:after { content: ""; position: absolute; left: 0; top: 0; width: 100%; height: 100%; animation: 1s ease-out flare-down; } @keyframes flare-down { from { background: rgba(255, 50, 50, 0.5); } } </style> <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>GrapeCity Wijmo FlexGrid Dynamic Updates</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> import 'bootstrap.css'; import '@grapecity/wijmo.styles/wijmo.css'; import './app.css'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; import * as wjInput from '@grapecity/wijmo.react.input'; import * as wjFlexGrid from '@grapecity/wijmo.react.grid'; import * as wjFlexGridFilter from '@grapecity/wijmo.react.grid.filter'; import * as wjcCore from '@grapecity/wijmo'; class App extends React.Component { constructor(props) { super(props); this._clearCells = false; this._cellElements = {}; this.intervalItems = [1000, 500, 100, 10, 1]; this.batchSizeItems = [100, 50, 10, 5, 1]; this.state = { data: this._getData(200), customCells: true, autoUpdate: true, interval: 100, batchSize: 5 }; this.initializeGrid = this.initializeGrid.bind(this); this.customCellsChange = this.customCellsChange.bind(this); this.autoUpdateChange = this.autoUpdateChange.bind(this); this.intervalItemsChange = this.intervalItemsChange.bind(this); this.batchSizeItemsChange = this.batchSizeItemsChange.bind(this); } initializeGrid(flex) { this.flex = flex; flex.rowHeaders.columns[0].width = 80; flex.updatingView.addHandler(() => { this._clearCells = true; // clear cell elements on next formatItem }); flex.formatItem.addHandler((s, e) => { // // show symbols in row headers if (e.panel == s.rowHeaders && e.col == 0) { e.cell.textContent = item = s.rows[e.row].dataItem.symbol; } // // regular cells if (e.panel == s.cells) { var col = s.columns[e.col], item = s.rows[e.row].dataItem; // // clear cell elements if (this._clearCells) { this._clearCells = false; this._cellElements = {}; } // // store cell element if (!this._cellElements[item.symbol]) { this._cellElements[item.symbol] = { item: item }; } this._cellElements[item.symbol][col.binding] = e.cell; // // custom painting this._formatCell(e.cell, item, col, false); } }); this._updateTrades(); } invalidateGrid() { this.flex.invalidate(); } _getData(cnt) { // Companies in the Financial Times Stock Exchange 100 Index // https://en.wikipedia.org/wiki/FTSE_100_Index // mcap: market capitalization (in billion pounds) // emp: employees (thousands) let companies = [ { symbol: 'RDSA', name: 'Royal Dutch Shell', sector: 'Oil and gas', mcap: 160.12, emp: 90 }, { symbol: 'ULVR', name: 'Unilever', sector: 'Consumer goods', mcap: 90.42, emp: 171 }, { symbol: 'HSBA', name: 'HSBC', sector: 'Banking', mcap: 88.11, emp: 267 }, { symbol: 'BATS', name: 'British American Tobacco', sector: 'Tobacco', mcap: 71.4, emp: 87 }, { symbol: 'GSK', name: 'GlaxoSmithKline', sector: 'Pharmaceuticals', mcap: 67.38, emp: 97 }, { symbol: 'SAB', name: 'SABMiller', sector: 'Beverages', mcap: 67.32, emp: 70 }, { symbol: 'BP', name: 'BP', sector: 'Oil and gas', mcap: 63.13, emp: 97 }, { symbol: 'VOD', name: 'Vodafone Group', sector: 'Telecomms', mcap: 56.55, emp: 86 }, { symbol: 'AZN', name: 'AstraZeneca', sector: 'Pharmaceuticals', mcap: 51.23, emp: 57 }, { symbol: 'RB', name: 'Reckitt Benckiser', sector: 'Consumer goods', mcap: 46.32, emp: 32 }, { symbol: 'DGE', name: 'Diageo', sector: 'Beverages', mcap: 46.01, emp: 25 }, { symbol: 'BT.A', name: 'BT Group', sector: 'Telecomms', mcap: 45.61, emp: 89 }, { symbol: 'LLOY', name: 'Lloyds Banking Group', sector: 'Banking', mcap: 44.11, emp: 120 }, { symbol: 'BLT', name: 'BHP Billiton', sector: 'Mining', mcap: 41.88, emp: 46 }, { symbol: 'NG', name: 'National Grid plc', sector: 'Energy', mcap: 36.14, emp: 27 }, { symbol: 'IMB', name: 'Imperial Brands', sector: 'Tobacco', mcap: 35.78, emp: 38 }, { symbol: 'RIO', name: 'Rio Tinto Group', sector: 'Mining', mcap: 34.84, emp: 67 }, { symbol: 'PRU', name: 'Prudential plc', sector: 'Finance', mcap: 31.63, emp: 25 }, { symbol: 'RBS', name: 'Royal Bank of Scotland Group', sector: 'Banking', mcap: 28.6, emp: 150 }, { symbol: 'BARC', name: 'Barclays', sector: 'Banking', mcap: 27.18, emp: 150 }, { symbol: 'ABF', name: 'Associated British Foods', sector: 'Food', mcap: 25.77, emp: 102 }, { symbol: 'REL', name: 'RELX Group', sector: 'Publishing', mcap: 25.54, emp: 28 }, { symbol: 'REX', name: 'Rexam', sector: 'Packaging', mcap: 25.54, emp: 19 }, { symbol: 'CCL', name: 'Carnival Corporation & plc', sector: 'Leisure', mcap: 24.85, emp: 86 }, { symbol: 'SHP', name: 'Shire plc', sector: 'Pharmaceuticals', mcap: 22.52, emp: 4 }, { symbol: 'CPG', name: 'Compass Group', sector: 'Food', mcap: 20.21, emp: 471 }, { symbol: 'WPP', name: 'WPP plc', sector: 'Media', mcap: 19.01, emp: 162 }, { symbol: 'AV.', name: 'Aviva', sector: 'Insurance', mcap: 17.69, emp: 40 }, { symbol: 'SKY', name: 'Sky plc', sector: 'Media', mcap: 17.5, emp: 22 }, { symbol: 'GLEN', name: 'Glencore', sector: 'Mining', mcap: 16.96, emp: 57 }, { symbol: 'BA.', name: 'BAE Systems', sector: 'Military', mcap: 16.01, emp: 107 }, { symbol: 'TSCO', name: 'Tesco', sector: 'Supermarket', mcap: 14.92, emp: 519 }, { symbol: 'SSE', name: 'SSE plc', sector: 'Energy', mcap: 14.03, emp: 19 }, { symbol: 'STAN', name: 'Standard Chartered', sector: 'Banking', mcap: 13.52, emp: 86 }, { symbol: 'LGEN', name: 'Legal & General', sector: 'Insurance', mcap: 13.21, emp: 9 }, { symbol: 'ARM', name: 'ARM Holdings', sector: 'Engineering', mcap: 13.2, emp: 2 }, { symbol: 'RR.', name: 'Rolls-Royce Holdings', sector: 'Manufacturing', mcap: 11.8, emp: 55 }, { symbol: 'EXPN', name: 'Experian', sector: 'Information', mcap: 11.1, emp: 17 }, { symbol: 'IAG', name: 'International Consolidated Airlines Group SA', sector: 'Travel', mcap: 11.01, emp: 58 }, { symbol: 'CRH', name: 'CRH plc', sector: 'Building materials', mcap: 10.9, emp: 76 }, { symbol: 'CNA', name: 'Centrica', sector: 'Energy', mcap: 10.72, emp: 40 }, { symbol: 'SN.', name: 'Smith & Nephew', sector: 'Medical', mcap: 10.27, emp: 11 }, { symbol: 'ITV', name: 'ITV plc', sector: 'Media', mcap: 10.15, emp: 4 }, { symbol: 'WOS', name: 'Wolseley plc', sector: 'Building materials', mcap: 9.2, emp: 44 }, { symbol: 'OML', name: 'Old Mutual', sector: 'Insurance', mcap: 8.45, emp: 54 }, { symbol: 'LAND', name: 'Land Securities', sector: 'Property', mcap: 8.19, emp: 0 }, { symbol: 'LSE', name: 'London Stock Exchange Group', sector: 'Finance', mcap: 8.06, emp: 4 }, { symbol: 'KGF', name: 'Kingfisher plc', sector: 'Retail homeware', mcap: 7.8, emp: 80 }, { symbol: 'CPI', name: 'Capita', sector: 'Support Services', mcap: 7.38, emp: 46 }, { symbol: 'BLND', name: 'British Land', sector: 'Property', mcap: 7.13, emp: 0 }, { symbol: 'WTB', name: 'Whitbread', sector: 'Retail hospitality', mcap: 7.09, emp: 86 }, { symbol: 'MKS', name: 'Marks & Spencer', sector: 'Retailer', mcap: 7.01, emp: 81 }, { symbol: 'FRES', name: 'Fresnillo plc', sector: 'Mining', mcap: 6.99, emp: 2 }, { symbol: 'NXT', name: 'Next plc', sector: 'Retail clothing', mcap: 6.9, emp: 58 }, { symbol: 'SDR', name: 'Schroders', sector: 'Fund management', mcap: 6.63, emp: 3 }, { symbol: 'SL', name: 'Standard Life', sector: 'Fund management', mcap: 6.63, emp: 10 }, { symbol: 'PSON', name: 'Pearson PLC', sector: 'Education', mcap: 6.52, emp: 37 }, { symbol: 'BNZL', name: 'Bunzl', sector: 'Industrial products', mcap: 6.38, emp: 12 }, { symbol: 'MNDI', name: 'Mondi', sector: 'Manufacturing', mcap: 6.37, emp: 26 }, { symbol: 'UU', name: 'United Utilities', sector: 'Water', mcap: 6.36, emp: 5 }, { symbol: 'PSN', name: 'Persimmon plc', sector: 'Building', mcap: 6.34, emp: 2 }, { symbol: 'SGE', name: 'Sage Group', sector: 'IT', mcap: 6.26, emp: 12 }, { symbol: 'EZJ', name: 'EasyJet', sector: 'Travel', mcap: 6.17, emp: 11 }, { symbol: 'AAL', name: 'Anglo American plc', sector: 'Mining', mcap: 6.09, emp: 100 }, { symbol: 'TW.', name: 'Taylor Wimpey', sector: 'Building', mcap: 5.99, emp: 3 }, { symbol: 'TUI', name: 'TUI Group', sector: 'Leisure', mcap: 5.99, emp: 76 }, { symbol: 'WPG', name: 'Worldpay', sector: 'Payment services', mcap: 5.9, emp: 4 }, { symbol: 'RRS', name: 'Randgold Resources', sector: 'Mining', mcap: 5.89, emp: 6 }, { symbol: 'HL', name: 'Hargreaves Lansdown', sector: 'Finance', mcap: 5.87, emp: 0 }, { symbol: 'BDEV', name: 'Barratt Developments', sector: 'Building', mcap: 5.86, emp: 5 }, { symbol: 'IHG', name: 'InterContinental Hotels Group', sector: 'Hotels', mcap: 5.75, emp: 345 }, { symbol: 'BRBY', name: 'Burberry', sector: 'Fashion', mcap: 5.65, emp: 10 }, { symbol: 'DC.', name: 'Dixons Carphone', sector: 'Retail', mcap: 5.16, emp: 40 }, { symbol: 'DLG', name: 'Direct Line Group', sector: 'Insurance', mcap: 5.15, emp: 13 }, { symbol: 'CCH', name: 'Coca-Cola HBC AG', sector: 'Consumer', mcap: 5.1, emp: 38 }, { symbol: 'SVT', name: 'Severn Trent', sector: 'Water', mcap: 5.04, emp: 8 }, { symbol: 'DCC', name: 'DCC plc', sector: 'Investments', mcap: 5.03, emp: 9 }, { symbol: 'SBRY', name: 'Sainsbury\'s', sector: 'Supermarket', mcap: 5.02, emp: 150 }, { symbol: 'ADM', name: 'Admiral Group', sector: 'Insurance', mcap: 4.91, emp: 2 }, { symbol: 'GKN', name: 'GKN', sector: 'Manufacturing', mcap: 4.79, emp: 50 }, { symbol: 'JMAT', name: 'Johnson Matthey', sector: 'Chemicals', mcap: 4.79, emp: 9 }, { symbol: 'PFG', name: 'Provident Financial', sector: 'Finance', mcap: 4.74, emp: 3 }, { symbol: 'ANTO', name: 'Antofagasta', sector: 'Mining', mcap: 4.71, emp: 4 }, { symbol: 'STJ', name: 'St. James\'s Place plc', sector: 'Finance', mcap: 4.68, emp: 1 }, { symbol: 'ITRK', name: 'Intertek', sector: 'Product testing', mcap: 4.67, emp: 33 }, { symbol: 'BAB', name: 'Babcock International', sector: 'Engineering', mcap: 4.65, emp: 34 }, { symbol: 'BKG', name: 'Berkeley Group Holdings', sector: 'Building', mcap: 4.6, emp: 2 }, { symbol: 'ISAT', name: 'Inmarsat', sector: 'Telecomms', mcap: 4.47, emp: 1 }, { symbol: 'TPK', name: 'Travis Perkins', sector: 'Retailer', mcap: 4.46, emp: 24 }, { symbol: 'HMSO', name: 'Hammerson', sector: 'Property', mcap: 4.42, emp: 0 }, { symbol: 'MERL', name: 'Merlin Entertainments', sector: 'Leisure', mcap: 4.42, emp: 28 }, { symbol: 'RMG', name: 'Royal Mail', sector: 'Delivery', mcap: 4.41, emp: 150 }, { symbol: 'AHT', name: 'Ashtead Group', sector: 'Equipment rental', mcap: 4.26, emp: 12 }, { symbol: 'RSA', name: 'RSA Insurance Group', sector: 'Insurance', mcap: 4.16, emp: 21 }, { symbol: 'III', name: '3i', sector: 'Private equity', mcap: 4.06, emp: 0 }, { symbol: 'INTU', name: 'Intu Properties', sector: 'Property', mcap: 3.89, emp: 2 }, { symbol: 'SMIN', name: 'Smiths Group', sector: 'Engineering', mcap: 3.84, emp: 23 }, { symbol: 'HIK', name: 'Hikma Pharmaceuticals', sector: 'Manufacturing', mcap: 3.71, emp: 6 }, { symbol: 'ADN', name: 'Aberdeen Asset Management', sector: 'Fund management', mcap: 3.14, emp: 1 }, { symbol: 'SPD', name: 'Sports Direct', sector: 'Retail', mcap: 2.4, emp: 17 } ]; // Trading Market Data // https://en.wikipedia.org/wiki/Market_data let data = [], now = new Date(); companies.forEach((company) => { let bid = this._randBetween(1, 100000) / 100, ask = bid + this._randBetween(0, 100) / 100; data.push({ symbol: company.symbol, name: company.name, bid: bid, ask: ask, lastSale: bid, bidSize: this._randBetween(10, 500), askSize: this._randBetween(10, 500), lastSize: this._randBetween(10, 500), volume: this._randBetween(10000, 20000), quoteTime: now, tradeTime: now, askHistory: [ask, ask], bidHistory: [bid, bid], saleHistory: [bid, bid] }); }); // done return data; } // get a random number within a given interval _randBetween(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); } // custom cell painting _formatCell(cell, item, col, flare) { if (this.state.customCells) { switch (col.binding) { case 'bid': this._formatDynamicCell(cell, item, col, 'bidHistory', flare); break; case 'ask': this._formatDynamicCell(cell, item, col, 'askHistory', flare); break; case 'lastSale': this._formatDynamicCell(cell, item, col, 'saleHistory', flare); break; default: cell.textContent = wjcCore.Globalize.format(item[col.binding], col.format); break; } } else { cell.textContent = wjcCore.Globalize.format(item[col.binding], col.format); } } _formatDynamicCell(cell, item, col, history, flare) { // cell template let html = '<div class="ticker chg-{dir} flare-{fdir}"> ' + '<div class="value">{value}</div >' + '<div class="chg">{chg}</div>' + '<div class="glyph"><span class="wj-glyph-{glyph}"></span></div>' + '<div class="spark">{spark}</div>' + '</div>'; // value html = html.replace('{value}', wjcCore.Globalize.format(item[col.binding], col.format)); // % change let hist = item[history]; let chg = hist.length > 1 ? hist[hist.length - 1] / hist[hist.length - 2] - 1 : 0; html = html.replace('{chg}', wjcCore.Globalize.format(chg * 100, 'n1') + '%'); // up/down glyph let glyph = chg > +0.001 ? 'up' : chg < -0.001 ? 'down' : 'circle'; html = html.replace('{glyph}', glyph); // sparklines html = html.replace('{spark}', this._getSparklines(item[history])); // change direction let dir = glyph == 'circle' ? 'none' : glyph; html = html.replace('{dir}', dir); // flare direction let flareDir = flare ? dir : 'none'; html = html.replace('{fdir}', flareDir); // done cell.innerHTML = html; } // // update grid cells when items change _updateGrid(changedItems) { for (let symbol in changedItems) { let itemCells = this._cellElements[symbol]; if (itemCells) { let item = itemCells.item; this.flex.columns.forEach((col) => { let cell = itemCells[col.binding]; if (cell) { this._formatCell(cell, item, col, true); } }); } } } _updateTrades() { let now = new Date(); let changedItems = {}; for (let i = 0; i < this.state.batchSize; i++) { // select an item let item = this.state.data[this._randBetween(0, this.state.data.length - 1)]; // update current data item.bid = item.bid * (1 + (Math.random() * .11 - .05)); item.ask = item.ask * (1 + (Math.random() * .11 - .05)); item.bidSize = this._randBetween(10, 1000); item.askSize = this._randBetween(10, 1000); var sale = (item.ask + item.bid) / 2; item.lastSale = sale; item.lastSize = Math.floor((item.askSize + item.bidSize) / 2); item.quoteTime = now; item.tradeTime = new Date(Date.now() + this._randBetween(0, 60000)); // update history data this._addHistory(item.askHistory, item.ask); this._addHistory(item.bidHistory, item.bid); this._addHistory(item.saleHistory, item.lastSale); // // keep track of changed items changedItems[item.symbol] = true; } // // update the grid if (this.state.autoUpdate) { this._updateGrid(changedItems); } // // and schedule the next batch setTimeout(this._updateTrades.bind(this), this.state.interval); } // add a value to a history array _addHistory(array, data) { array.push(data); if (array.length > 10) { // limit history length array.splice(0, 1); } } // generate sparklines as SVG _getSparklines(data) { let svg = '', 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); let y2 = this._scaleY(data[i], min, max); svg += '<line x1=' + x1 + '% y1=' + y1 + '% x2=' + x2 + '% y2=' + y2 + '% />'; x1 = x2; y1 = y2; } return '<svg><g>' + svg + '</g></svg>'; } _scaleY(value, min, max) { return max > min ? 100 - Math.round((value - min) / (max - min) * 100) : 0; } customCellsChange() { this.setState({ customCells: !this.state.customCells }); this.invalidateGrid(); } autoUpdateChange() { this.setState({ autoUpdate: !this.state.autoUpdate }); } intervalItemsChange(s, e) { this.setState({ interval: s.selectedValue }); } batchSizeItemsChange(s, e) { this.setState({ batchSize: s.selectedValue }); } render() { return <div className="container-fluid"> <label> Custom Cells <input onChange={this.customCellsChange} checked={this.state.customCells} type="checkbox"/> </label> <label> Auto Update <input onChange={this.autoUpdateChange} checked={this.state.autoUpdate} type="checkbox"/> </label> <label> Update Interval (ms) <wjInput.ComboBox itemsSource={this.intervalItems} selectedValue={this.state.interval} textChanged={this.intervalItemsChange}></wjInput.ComboBox> </label> <label> Batch Size (# items) <wjInput.ComboBox itemsSource={this.batchSizeItems} selectedValue={this.state.batchSize} textChanged={this.batchSizeItemsChange}></wjInput.ComboBox> </label> <wjFlexGrid.FlexGrid isReadOnly={true} selectionMode="Row" initialized={this.initializeGrid} itemsSource={this.state.data}> <wjFlexGridFilter.FlexGridFilter></wjFlexGridFilter.FlexGridFilter> <wjFlexGrid.FlexGridColumn binding="name" header="Name" width={200}></wjFlexGrid.FlexGridColumn> <wjFlexGrid.FlexGridColumn binding="bid" header="Bid" width={200} format="n2"></wjFlexGrid.FlexGridColumn> <wjFlexGrid.FlexGridColumn binding="ask" header="Ask" width={200} format="n2"></wjFlexGrid.FlexGridColumn> <wjFlexGrid.FlexGridColumn binding="lastSale" header="Last Sale" width={200} format="n2"></wjFlexGrid.FlexGridColumn> <wjFlexGrid.FlexGridColumn binding="bidSize" header="Bid Size" format="n0"></wjFlexGrid.FlexGridColumn> <wjFlexGrid.FlexGridColumn binding="askSize" header="Ask Size" format="n0"></wjFlexGrid.FlexGridColumn> <wjFlexGrid.FlexGridColumn binding="lastSize" header="Last Size" format="n0"></wjFlexGrid.FlexGridColumn> <wjFlexGrid.FlexGridColumn binding="volume" header="Volume" format="n0"></wjFlexGrid.FlexGridColumn> <wjFlexGrid.FlexGridColumn binding="quoteTime" header="Quote Time" format="hh:mm:ss" align="center"></wjFlexGrid.FlexGridColumn> <wjFlexGrid.FlexGridColumn binding="tradeTime" header="Trade Time" format="hh:mm:ss" align="center"></wjFlexGrid.FlexGridColumn> </wjFlexGrid.FlexGrid> </div>; } } 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>GrapeCity Wijmo FlexGrid Dynamic Updates</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> label { font-weight: normal; margin-right: 20px; } body { margin-bottom: 40px; } /* Wijmo Controls */ .wj-combobox { width: 100px; } .wj-flexgrid { max-height: 500px; } .wj-flexgrid .wj-cell { padding: 6px; } .wj-flexgrid .wj-cell:not(.wj-header) { border-bottom: none; } /* ticker cell */ .ticker div { display: inline-block; } .ticker .chg { font-size: 75%; opacity: .75; text-align: center; width: 4em; } .ticker .glyph { font-size: 120%; text-align: center; width: 1em; } .ticker .spark { padding: 0 4px; width: 4em; height: 1em; opacity: .65; } .ticker .spark svg { width: 100%; height: 100%; stroke: currentColor; stroke-width: 2px; overflow: visible; } /* value going up */ .wj-cell:not(.wj-state-selected):not(.wj-state-multi-selected) .ticker.chg-up .chg, .wj-cell:not(.wj-state-selected):not(.wj-state-multi-selected) .ticker.chg-up .glyph { color: green; } /* value going down */ .wj-cell:not(.wj-state-selected):not(.wj-state-multi-selected) .ticker.chg-down .chg, .wj-cell:not(.wj-state-selected):not(.wj-state-multi-selected) .ticker.chg-down .glyph { color: red; } /* value not changing */ .ticker.chg-none .chg, .ticker.chg-none .glyph { opacity: .25; } /* up/down 'flare' animations */ .ticker.flare-up:after { content: ''; position: absolute; left: 0; top: 0; width: 100%; height: 100%; animation: 1s ease-out flare-up; } @keyframes flare-up { from { background: rgba(50, 255, 50, 0.5); } } .ticker.flare-down:after { content: ''; position: absolute; left: 0; top: 0; width: 100%; height: 100%; animation: 1s ease-out flare-down; } @keyframes flare-down { from { background: rgba(255, 50, 50, 0.5); } }