AG-420 Improve React implementation

This commit is contained in:
Sean Landsman
2017-05-23 17:51:33 +01:00
parent e183a0503a
commit 06ebb8bf18
11 changed files with 217 additions and 321 deletions

View File

@@ -0,0 +1,16 @@
import cloneDeep from "lodash/cloneDeep";
export function fxDataUpdated(fxData) {
return {
type: 'FX_DATA_CHANGED',
fxData: cloneDeep(fxData)
};
}
export function fxTopMoversUpdated(fxTopMovers) {
return {
type: 'FX_TOP_MOVERS_CHANGED',
fxTopMovers: cloneDeep(fxTopMovers)
};
}

View File

@@ -1,9 +0,0 @@
import cloneDeep from "lodash/cloneDeep";
export default function fxDataUpdatedAction(fxData) {
return {
type: 'FX_DATA_CHANGED',
fxData: cloneDeep(fxData)
};
}

View File

@@ -0,0 +1,26 @@
import React, {Component} from "react";
import FxDataService from "../services/FxDataService.jsx";
import FxQuoteMatrix from "./FxQuoteMatrix.jsx";
import TopMoversGrid from "./TopMoversGrid.jsx";
export default class extends Component {
constructor(props) {
super(props);
this.fxDataService = new FxDataService();
}
render() {
return (
<div>
<div style={{float: "left", marginRight: 25}}>
<FxQuoteMatrix fxDataService={this.fxDataService}/>
</div>
<div style={{float: "left"}}>
<TopMoversGrid fxDataService={this.fxDataService}/>
</div>
</div>
);
}
};

View File

@@ -5,18 +5,13 @@ import {AgGridReact} from "ag-grid-react";
import assign from "lodash/assign";
import uniq from "lodash/uniq";
import cloneDeep from "lodash/cloneDeep";
import FxDataService from "../services/FxDataService.jsx";
class FxQuoteMatrix extends Component {
constructor(props) {
super(props);
this.fxDataService = new FxDataService();
this.state = {
columnDefs: this.fxDataService.getFxMatrixHeaderNames()
columnDefs: this.props.fxDataService.getFxMatrixHeaderNames()
};
// grid events
@@ -28,48 +23,43 @@ class FxQuoteMatrix extends Component {
this.columnApi = params.columnApi;
}
componentDidMount() {
this.gridApi.setRowData(this.props.rowData);
}
componentWillReceiveProps(nextProps) {
if (!this.gridApi) {
return;
}
const newRowData = nextProps.rowData;
if (!this.props.rowData ||
this.props.rowData.length === 0) {
this.gridApi.setRowData(nextProps.rowData);
} else {
const newRowData = nextProps.rowData;
const updatedNodes = [];
const updatedCols = [];
const updatedNodes = [];
const updatedCols = [];
for (let i = 0; i < newRowData.length; i++) {
// note that for this use case we assume the existing and new row data have the same
// row and column order
let node = this.gridApi.getModel().getRow(i);
let newRow = newRowData[i];
for (let i = 0; i < newRowData.length; i++) {
// note that for this use case we assume the existing and new row data have the same
// row and column order
let node = this.gridApi.getModel().getRow(i);
let newRow = newRowData[i];
const {data} = node;
let updated = false;
for (const def of this.state.columnDefs) {
if (data[def.field] !== newRow[def.field]) {
updatedCols.push(def.field);
const {data} = node;
let updated = false;
for (const def of this.state.columnDefs) {
if (data[def.field] !== newRow[def.field]) {
updatedCols.push(def.field);
updated = true;
}
}
if(updated) {
assign(data, newRow);
updatedNodes.push(node);
updated = true;
}
}
this.gridApi.refreshCells(updatedNodes, uniq(updatedCols));
if(updated) {
assign(data, newRow);
updatedNodes.push(node);
}
}
this.gridApi.refreshCells(updatedNodes, uniq(updatedCols));
}
render() {
return (
<div style={{height: 410, width: "100%"}}
<div style={{height: 410, width: 800}}
className="ag-fresh">
<AgGridReact
// properties

View File

@@ -41,15 +41,6 @@ export default class extends Component {
cellFormatter: this.numberFormatter,
cellRenderer: 'animateShowChange',
cellStyle: {'text-align': 'right'}
},
{
headerName: 'Recommendation',
field: 'recommendation',
cellEditor: 'richSelect',
cellEditorParams: {
values: ['Buy', 'Hold', 'Sell']
},
editable: true
}
]
};

View File

@@ -2,7 +2,7 @@ import React, {Component} from "react";
import PriceChangesGrid from "./PriceChangesGrid.jsx";
import StockDetailPanel from "./StockDetailPanel.jsx";
import FxQuoteMatrix from "./FxQuoteMatrix.jsx";
import FxPanel from "./FxPanel.jsx";
export default class extends Component {
constructor(props) {
@@ -48,7 +48,7 @@ export default class extends Component {
</div>
</div>
<div style={{width: "100%", clear: "both", paddingTop: 25}}>
<FxQuoteMatrix/>
<FxPanel/>
</div>
</div>
);

View File

@@ -0,0 +1,105 @@
import React, {Component} from "react";
import {connect} from "react-redux";
import {AgGridReact} from "ag-grid-react";
class TopMoversGrid extends Component {
constructor(props) {
super(props);
this.state = {
columnDefs: [
{
field: 'symbol',
headerName: 'Symbol'
},
{
field: 'last',
headerName: 'Last',
headerClass: 'align-right',
cellRenderer: 'animateShowChange',
cellClass: 'align-right'
},
{
field: 'net',
headerName: 'Net',
headerClass: 'align-right',
cellRenderer: 'animateShowChange',
cellClass: 'align-right'
},
{
field: 'pct_net_change',
headerName: '% NC',
headerClass: 'align-right',
cellRenderer: 'animateShowChange',
cellClass: 'align-right'
},
]
};
// grid events
this.onGridReady = this.onGridReady.bind(this);
}
onGridReady(params) {
this.gridApi = params.api;
this.columnApi = params.columnApi;
}
componentDidMount() {
this.gridApi.setRowData(this.props.rowData);
this.gridApi.sizeColumnsToFit();
}
componentWillReceiveProps(nextProps) {
let newRowData = nextProps.rowData;
let model = this.gridApi.getModel();
// remove nodes not in new data set
let nodesToRemove = [];
for (let rowIndex = 0; rowIndex < model.getRowCount(); rowIndex++) {
let rowNode = model.getRow(rowIndex);
if (rowNode.data.symbol !== newRowData[rowIndex].symbol) {
nodesToRemove.push(rowNode);
}
}
this.gridApi.removeItems(nodesToRemove);
// add new items in set
for (let rowIndex = 0; rowIndex < newRowData.length; rowIndex++) {
let model = this.gridApi.getModel();
let rowNode = model.getRow(rowIndex);
if (!rowNode ||
rowNode.data.symbol !== newRowData[rowIndex].symbol) {
this.gridApi.insertItemsAtIndex(rowIndex, [newRowData[rowIndex]])
}
}
}
render() {
return (
<div style={{height: 410, width: 400}}
className="ag-fresh">
<AgGridReact
// properties
columnDefs={this.state.columnDefs}
enableSorting="false"
enableFilter="false"
animateRows="true"
// events
onGridReady={this.onGridReady}>
</AgGridReact>
</div>
);
}
}
export default connect(
(state) => {
return {
rowData: state.fxTopMovers
}
}
)(TopMoversGrid);

View File

@@ -1,10 +0,0 @@
export default (state = {fxData: []}, action) => {
switch (action.type) {
case 'FX_DATA_CHANGED':
return {
fxData: action.fxData.slice(0)
};
default:
return state;
}
};

View File

@@ -0,0 +1,16 @@
export default (state = {fxData: [], fxTopMovers: []}, action) => {
switch (action.type) {
case 'FX_DATA_CHANGED':
return {
...state,
fxData: action.fxData.slice(0),
};
case 'FX_TOP_MOVERS_CHANGED':
return {
...state,
fxTopMovers: action.fxTopMovers.slice(0),
};
default:
return action.state;
}
};

View File

@@ -1,7 +1,8 @@
import sampleSize from "lodash/sampleSize";
import cloneDeep from "lodash/cloneDeep";
import StoreService from "./StoreService";
import fxDataUpdated from "../actions/fxDataUpdated";
import {fxDataUpdated, fxTopMoversUpdated} from "../actions/fxDataActions";
import HorizontalBarComponent from "../components/renderers/HorizontalBarComponent.jsx";
@@ -9,14 +10,32 @@ export default class {
constructor() {
this.store = StoreService.STORE;
this.fxData = this.createFxMatrixSnapshot();
this.createFxMatrixSnapshot();
this.calculateTopMovers();
this.store.dispatch(fxDataUpdated(this.fxData));
this.store.dispatch(fxTopMoversUpdated(this.fxTopMovers));
setInterval(() => {
this.applyDeltasToFxData();
this.store.dispatch(fxDataUpdated(this.fxData));
}, 1500)
}, 1500);
setInterval(() => {
this.calculateTopMovers();
this.store.dispatch(fxTopMoversUpdated(this.fxTopMovers));
}, 2500);
}
calculateTopMovers() {
let fxData = cloneDeep(this.fxData);
fxData.sort((a, b) => {
return Math.abs(b.pct_net_change) - Math.abs(a.pct_net_change);
});
this.fxTopMovers = fxData.slice(0, 20);
}
applyDeltasToFxData() {
@@ -34,7 +53,7 @@ export default class {
row['pct_net_change'] = multiplier * (this.random(30, 100) / 100).toFixed(2);
for (let symbolToUpdate of fxSymbolsToUpdate) {
if(row[symbolToUpdate] === null) {
if (row[symbolToUpdate] === null) {
continue;
}
@@ -42,7 +61,6 @@ export default class {
row[symbolToUpdate] = (multiplier * Math.random()).toFixed(2);
}
}
return this.fxData;
}
random(min, max) {
@@ -75,7 +93,8 @@ export default class {
rowData.push(row);
}
return rowData;
this.fxData = rowData;
}
}
@@ -169,251 +188,3 @@ const FX_CURRENCY_MATRIX = [
["CHFJPY", "0.60", "0.96", "0.22", "0.52", "0.63", "0.24", "0.76", "0.91", "0.80", "-0.14", "0.59", "-0.01", "0.60", "-0.08", "0.63", "0.39", "0.30", "-0.71", "0.18", "0.94", "-0.30", "0.28", "0.87", "0.92", "-0.19", "0.18", "-0.40", "0.44", "0.88", "0.52", "0.50", "0.44", "0.84", "0.24", "0.09", "0.15", "0.10", "0.30", "-0.05", "0.79", null, "0.99"],
["CHFCAD", "0.93", "0.24", "0.05", "0.40", "0.75", "0.12", "0.90", "0.56", "0.46", "0.62", "0.71", "0.01", "0.04", "0.02", "0.49", "0.51", "-0.94", "0.93", "0.80", "-0.40", "0.79", "0.82", "-0.04", "0.38", "-0.24", "-0.17", "0.78", "-0.42", "0.92", "-0.83", "-0.01", "0.54", "0.73", "0.52", "0.95", "0.44", "0.90", "0.81", "0.42", "0.26", "0.36", null]
];
// for (i = 0; i < MAJOR_CURRENCIES.length; i++) {
// for (j = 0; j < MAJOR_CURRENCIES.length; j++) {
// if (i === j) {
// continue;
// }
// console.log(`${MAJOR_CURRENCIES[i] + MAJOR_CURRENCIES[j]}`);
// }
// }
// let rows = [];
// let row = ['Symbol'];
//
// rows = [];
// for (j = 0; j < FX_CURRENCY_CODE.length; j++) {
// row.push(FX_CURRENCY_CODE[j]);
// }
// rows.push(row);
//
// for (i = 0; i < FX_CURRENCY_CODE.length; i++) {
// let row = [];
// row.push(FX_CURRENCY_CODE[i]);
//
// for (j = 0; j < FX_CURRENCY_CODE.length; j++) {
// if (i === j) {
// row.push(null);
// } else {
// var mutliplier = ((Math.random() * 10) > 8) ? -1 : 1;
// row.push((mutliplier * Math.random()).toFixed(2))
// // row.push(`${FX_CURRENCY_CODE[j]} value`)
// }
// }
//
// rows.push(row);
// }
//
// JSON.stringify(rows);
//
// const FX_CURRENCY_CODE = [
// 'USDGBP',
// 'USDEUR',
// 'USDAED',
// 'USDJPY',
// 'USDCAD',
// 'USDCHF',
// 'GBPUSD',
// 'GBPEUR',
// 'GBPAED',
// 'GBPJPY',
// 'GBPCAD',
// 'GBPCHF',
// 'EURUSD',
// 'EURGBP',
// 'EURAED',
// 'EURJPY',
// 'EURCAD',
// 'EURCHF',
// 'AEDUSD',
// 'AEDGBP',
// 'AEDEUR',
// 'AEDJPY',
// 'AEDCAD',
// 'AEDCHF',
// 'JPYUSD',
// 'JPYGBP',
// 'JPYEUR',
// 'JPYAED',
// 'JPYCAD',
// 'JPYCHF',
// 'CADUSD',
// 'CADGBP',
// 'CADEUR',
// 'CADAED',
// 'CADJPY',
// 'CADCHF',
// 'CHFUSD',
// 'CHFGBP',
// 'CHFEUR',
// 'CHFAED',
// 'CHFJPY',
// 'CHFCAD'
// ];
//
// const MAJOR_CURRENCIES = [
// 'USD',
// 'GBP',
// 'EUR',
// 'AED',
// 'JPY',
// 'CAD',
// 'CHF'];
//
// const SECONDARY_CURRENCIES = [
// 'AFN',
// 'ALL',
// 'AMD',
// 'ANG',
// 'AOA',
// 'ARS',
// 'AUD',
// 'AWG',
// 'AZN',
// 'BAM',
// 'BBD',
// 'BDT',
// 'BGN',
// 'BHD',
// 'BIF',
// 'BMD',
// 'BND',
// 'BOB',
// 'BRL',
// 'BSD',
// 'BTN',
// 'BWP',
// 'BYN',
// 'BZD',
// 'CDF',
// 'CLP',
// 'CNY',
// 'COP',
// 'CRC',
// 'CUC',
// 'CUP',
// 'CVE',
// 'CZK',
// 'DJF',
// 'DKK',
// 'DOP',
// 'DZD',
// 'EGP',
// 'ERN',
// 'ETB',
// 'FJD',
// 'FKP',
// 'GEL',
// 'GGP',
// 'GHS',
// 'GIP',
// 'GMD',
// 'GNF',
// 'GTQ',
// 'GYD',
// 'HKD',
// 'HNL',
// 'HRK',
// 'HTG',
// 'HUF',
// 'IDR',
// 'ILS',
// 'IMP',
// 'INR',
// 'IQD',
// 'IRR',
// 'ISK',
// 'JEP',
// 'JMD',
// 'JOD',
// 'KES',
// 'KGS',
// 'KHR',
// 'KMF',
// 'KPW',
// 'KRW',
// 'KWD',
// 'KYD',
// 'KZT',
// 'LAK',
// 'LBP',
// 'LKR',
// 'LRD',
// 'LSL',
// 'LYD',
// 'MAD',
// 'MDL',
// 'MGA',
// 'MKD',
// 'MMK',
// 'MNT',
// 'MOP',
// 'MRO',
// 'MUR',
// 'MVR',
// 'MWK',
// 'MXN',
// 'MYR',
// 'MZN',
// 'NAD',
// 'NGN',
// 'NIO',
// 'NOK',
// 'NPR',
// 'NZD',
// 'OMR',
// 'PAB',
// 'PEN',
// 'PGK',
// 'PHP',
// 'PKR',
// 'PLN',
// 'PYG',
// 'QAR',
// 'RON',
// 'RSD',
// 'RUB',
// 'RWF',
// 'SAR',
// 'SBD',
// 'SCR',
// 'SDG',
// 'SEK',
// 'SGD',
// 'SHP',
// 'SLL',
// 'SOS',
// 'SPL',
// 'SRD',
// 'STD',
// 'SVC',
// 'SYP',
// 'SZL',
// 'THB',
// 'TJS',
// 'TMT',
// 'TND',
// 'TOP',
// 'TRY',
// 'TTD',
// 'TVD',
// 'TWD',
// 'TZS',
// 'UAH',
// 'UGX',
// 'UYU',
// 'UZS',
// 'VEF',
// 'VND',
// 'VUV',
// 'WST',
// 'XAF',
// 'XCD',
// 'XDR',
// 'XOF',
// 'XPF',
// 'YER',
// 'ZMW',
// 'ZWD',
// 'ZAR'];

View File

@@ -1,6 +1,6 @@
import {createStore} from "redux";
import fxData from "../reducers/fxData";
import fxData from "../reducers/fxDataReducer.jsx";
class StoreService {
}