グリッド:自動更新

データソースに変更があると、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: '銘柄', width: 200 }, { binding: 'bid', header: '買気配', format: 'n2', width: 200 }, { binding: 'ask', header: '売気配', format: 'n2', width: 200 }, { binding: 'lastSale', header: '直近約定値', format: 'n2', width: 200 }, { binding: 'bidSize', header: '買数量', format: 'n0' }, { binding: 'askSize', header: '売数量', format: 'n0' }, { binding: 'lastSize', header: '直近数量', format: 'n0' }, { binding: 'volume', header: '出来高', format: 'n0' }, { binding: 'quoteTime', header: '提示時刻', format: 'hh:mm:ss', align: 'center' }, { binding: 'tradeTime', header: '取引時刻', 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> カスタムセル <input id="customCells" type="checkbox" checked="checked"> </label> <label> 自動更新 <input id="autoUpdate" type="checkbox" checked="checked"> </label> <label> 更新間隔(ms) <input id="updateInterval"> </label> <label> 更新する項目数 <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> カスタムセル <input [(ngModel)]="customCells" (click)="invalidateGrid()" type="checkbox" /> </label> <label> 自動更新 <input [(ngModel)]="autoUpdate" type="checkbox" /> </label> <label> 更新間隔(ms) <wj-combo-box [itemsSource]="intervalItems" [(selectedValue)]="interval"></wj-combo-box> </label> <label> 更新する項目数 <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]="'銘柄'" [width]="200"></wj-flex-grid-column> <wj-flex-grid-column [binding]="'bid'" [header]="'買気配'" [width]="200" [format]="'n2'"></wj-flex-grid-column> <wj-flex-grid-column [binding]="'ask'" [header]="'売気配'" [width]="200" [format]="'n2'"></wj-flex-grid-column> <wj-flex-grid-column [binding]="'lastSale'" [header]="'直近約定値'" [width]="200" [format]="'n2'"></wj-flex-grid-column> <wj-flex-grid-column [binding]="'bidSize'" [header]="'買数量'" [format]="'n0'"></wj-flex-grid-column> <wj-flex-grid-column [binding]="'askSize'" [header]="'売数量'" [format]="'n0'"></wj-flex-grid-column> <wj-flex-grid-column [binding]="'lastSize'" [header]="'直近数量'" [format]="'n0'"></wj-flex-grid-column> <wj-flex-grid-column [binding]="'volume'" [header]="'出来高'" [format]="'n0'"></wj-flex-grid-column> <wj-flex-grid-column [binding]="'quoteTime'" [header]="'提示時刻'" [format]="'hh:mm:ss'" [align]="'center'"></wj-flex-grid-column> <wj-flex-grid-column [binding]="'tradeTime'" [header]="'取引時刻'" [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); } } 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); } render() { return <div className="container-fluid"> <label> カスタムセル <input onChange={this.customCellsChange} checked={this.state.customCells} type="checkbox"/> </label> <label> 自動更新 <input onChange={this.autoUpdateChange} checked={this.state.autoUpdate} type="checkbox"/> </label> <label> 更新間隔(ms) <wjInput.ComboBox itemsSource={this.intervalItems} selectedValue={this.state.interval} textChanged={this.intervalItemsChange}></wjInput.ComboBox> </label> <label> 更新する項目数 <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="銘柄" width={200}></wjFlexGrid.FlexGridColumn> <wjFlexGrid.FlexGridColumn binding="bid" header="買気配" width={200} format="n2"></wjFlexGrid.FlexGridColumn> <wjFlexGrid.FlexGridColumn binding="ask" header="売気配" width={200} format="n2"></wjFlexGrid.FlexGridColumn> <wjFlexGrid.FlexGridColumn binding="lastSale" header="直近約定値" width={200} format="n2"></wjFlexGrid.FlexGridColumn> <wjFlexGrid.FlexGridColumn binding="bidSize" header="買数量" format="n0"></wjFlexGrid.FlexGridColumn> <wjFlexGrid.FlexGridColumn binding="askSize" header="売数量" format="n0"></wjFlexGrid.FlexGridColumn> <wjFlexGrid.FlexGridColumn binding="lastSize" header="直近数量" format="n0"></wjFlexGrid.FlexGridColumn> <wjFlexGrid.FlexGridColumn binding="volume" header="出来高" format="n0"></wjFlexGrid.FlexGridColumn> <wjFlexGrid.FlexGridColumn binding="quoteTime" header="提示時刻" format="hh:mm:ss" align="center"></wjFlexGrid.FlexGridColumn> <wjFlexGrid.FlexGridColumn binding="tradeTime" header="取引時刻" format="hh:mm:ss" align="center"></wjFlexGrid.FlexGridColumn> </wjFlexGrid.FlexGrid> </div>; } 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 }); } } 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); } } <template> <div class="container-fluid"> <label class="normal-weight"> カスタムセル <input v-model="customCells" @click="invalidateGrid" type="checkbox"> </label> <label class="normal-weight"> 自動更新 <input v-model="autoUpdate" type="checkbox"> </label> <label class="normal-weight">更新間隔(ms) <wj-combo-box :itemsSource="intervalItems" :selectedValue="interval" :textChanged="intervalItemsChange"></wj-combo-box> </label> <label class="normal-weight">更新する項目数 <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="'銘柄'" :width="200"></wj-flex-grid-column> <wj-flex-grid-column :binding="'bid'" :header="'買気配'" :width="200" :format="'n2'"></wj-flex-grid-column> <wj-flex-grid-column :binding="'ask'" :header="'売気配'" :width="200" :format="'n2'"></wj-flex-grid-column> <wj-flex-grid-column :binding="'lastSale'" :header="'直近約定値'" :width="200" :format="'n2'" ></wj-flex-grid-column> <wj-flex-grid-column :binding="'bidSize'" :header="'買数量'" :format="'n0'"></wj-flex-grid-column> <wj-flex-grid-column :binding="'askSize'" :header="'売数量'" :format="'n0'"></wj-flex-grid-column> <wj-flex-grid-column :binding="'lastSize'" :header="'直近数量'" :format="'n0'"></wj-flex-grid-column> <wj-flex-grid-column :binding="'volume'" :header="'出来高'" :format="'n0'"></wj-flex-grid-column> <wj-flex-grid-column :binding="'quoteTime'" :header="'提示時刻'" :format="'hh:mm:ss'" :align="'center'" ></wj-flex-grid-column> <wj-flex-grid-column :binding="'tradeTime'" :header="'取引時刻'" :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.normal-weight { 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>