diff --git a/images/alberto.png b/images/alberto.png
new file mode 100644
index 0000000..e35f976
Binary files /dev/null and b/images/alberto.png differ
diff --git a/images/favicon.ico b/images/favicon.ico
new file mode 100644
index 0000000..b338d73
Binary files /dev/null and b/images/favicon.ico differ
diff --git a/images/fire.png b/images/fire.png
new file mode 100644
index 0000000..2a81112
Binary files /dev/null and b/images/fire.png differ
diff --git a/images/flags/ar.png b/images/flags/ar.png
new file mode 100644
index 0000000..159e8fd
Binary files /dev/null and b/images/flags/ar.png differ
diff --git a/images/flags/br.png b/images/flags/br.png
new file mode 100644
index 0000000..a549555
Binary files /dev/null and b/images/flags/br.png differ
diff --git a/images/flags/co.png b/images/flags/co.png
new file mode 100644
index 0000000..477177b
Binary files /dev/null and b/images/flags/co.png differ
diff --git a/images/flags/de.png b/images/flags/de.png
new file mode 100644
index 0000000..2dbd9b0
Binary files /dev/null and b/images/flags/de.png differ
diff --git a/images/flags/es.png b/images/flags/es.png
new file mode 100644
index 0000000..4c31326
Binary files /dev/null and b/images/flags/es.png differ
diff --git a/images/flags/fr.png b/images/flags/fr.png
new file mode 100644
index 0000000..ac1412a
Binary files /dev/null and b/images/flags/fr.png differ
diff --git a/images/flags/gb.png b/images/flags/gb.png
new file mode 100644
index 0000000..be5145b
Binary files /dev/null and b/images/flags/gb.png differ
diff --git a/images/flags/gr.png b/images/flags/gr.png
new file mode 100644
index 0000000..f869622
Binary files /dev/null and b/images/flags/gr.png differ
diff --git a/images/flags/ie.png b/images/flags/ie.png
new file mode 100644
index 0000000..52e2ad4
Binary files /dev/null and b/images/flags/ie.png differ
diff --git a/images/flags/is.png b/images/flags/is.png
new file mode 100644
index 0000000..74c9e03
Binary files /dev/null and b/images/flags/is.png differ
diff --git a/images/flags/it.png b/images/flags/it.png
new file mode 100644
index 0000000..eb710fe
Binary files /dev/null and b/images/flags/it.png differ
diff --git a/images/flags/mt.png b/images/flags/mt.png
new file mode 100644
index 0000000..aef8060
Binary files /dev/null and b/images/flags/mt.png differ
diff --git a/images/flags/no.png b/images/flags/no.png
new file mode 100644
index 0000000..e14f90f
Binary files /dev/null and b/images/flags/no.png differ
diff --git a/images/flags/pe.png b/images/flags/pe.png
new file mode 100644
index 0000000..73e9465
Binary files /dev/null and b/images/flags/pe.png differ
diff --git a/images/flags/pt.png b/images/flags/pt.png
new file mode 100644
index 0000000..a532665
Binary files /dev/null and b/images/flags/pt.png differ
diff --git a/images/flags/se.png b/images/flags/se.png
new file mode 100644
index 0000000..9e12578
Binary files /dev/null and b/images/flags/se.png differ
diff --git a/images/flags/uy.png b/images/flags/uy.png
new file mode 100644
index 0000000..98057bd
Binary files /dev/null and b/images/flags/uy.png differ
diff --git a/images/flags/ve.png b/images/flags/ve.png
new file mode 100644
index 0000000..a1aef2a
Binary files /dev/null and b/images/flags/ve.png differ
diff --git a/images/frost.png b/images/frost.png
new file mode 100644
index 0000000..d4cdac7
Binary files /dev/null and b/images/frost.png differ
diff --git a/images/horse.png b/images/horse.png
new file mode 100644
index 0000000..660d861
Binary files /dev/null and b/images/horse.png differ
diff --git a/images/niall.png b/images/niall.png
new file mode 100644
index 0000000..7074010
Binary files /dev/null and b/images/niall.png differ
diff --git a/images/phone.png b/images/phone.png
new file mode 100644
index 0000000..fd2c11f
Binary files /dev/null and b/images/phone.png differ
diff --git a/images/sean.png b/images/sean.png
new file mode 100644
index 0000000..ebae714
Binary files /dev/null and b/images/sean.png differ
diff --git a/images/smiley-sad.png b/images/smiley-sad.png
new file mode 100644
index 0000000..122f8ae
Binary files /dev/null and b/images/smiley-sad.png differ
diff --git a/images/smiley.png b/images/smiley.png
new file mode 100644
index 0000000..b4e3290
Binary files /dev/null and b/images/smiley.png differ
diff --git a/images/statue.png b/images/statue.png
new file mode 100644
index 0000000..a95f57f
Binary files /dev/null and b/images/statue.png differ
diff --git a/images/sun.png b/images/sun.png
new file mode 100644
index 0000000..ff082c8
Binary files /dev/null and b/images/sun.png differ
diff --git a/package.json b/package.json
index 7e7c77c..1f40354 100644
--- a/package.json
+++ b/package.json
@@ -29,31 +29,34 @@
},
"homepage": "http://www.ag-grid.com/",
"devDependencies": {
- "babel-core": "6.24.x",
- "babel-loader": "6.4.x",
- "babel-preset-es2015": "6.24.x",
- "babel-preset-react": "6.24.x",
- "babel-preset-stage-0": "6.24.x",
- "babel-preset-stage-1": "6.24.x",
- "css-loader": "0.23.x",
- "style-loader": "0.13.x",
- "webpack": "1.12.x",
- "webpack-dev-server": "1.14.x"
- },
- "dependencies": {
- "ag-grid": "10.0.x",
- "ag-grid-enterprise": "10.0.x",
- "ag-grid-react": "10.0.x",
- "bootstrap": "^3.3.7",
- "d3": "4.9.1",
- "file-loader": "^0.11.1",
- "lodash": "4.17.4",
- "react": "15.5.x",
- "react-dom": "15.5.x",
- "react-redux": "5.0.x",
- "redux": "3.6.x",
- "rimraf": "2.5.x"
- }
+ "babel-core": "6.24.x",
+ "babel-loader": "6.4.x",
+ "babel-preset-es2015": "6.24.x",
+ "babel-preset-react": "6.24.x",
+ "babel-preset-stage-0": "6.24.x",
+ "babel-preset-stage-1": "6.24.x",
+ "css-loader": "0.23.x",
+ "mkdirp": "0.5.1",
+ "ncp": "2.0.0",
+ "rimraf": "2.5.x",
+ "style-loader": "0.13.x",
+ "webpack": "1.12.x",
+ "webpack-dev-server": "1.14.x"
+ },
+ "dependencies": {
+ "bootstrap": "3.3.7",
+ "d3": "4.9.1",
+ "file-loader": "0.11.1",
+ "lodash": "4.17.4",
+ "react": "15.5.x",
+ "react-dom": "15.5.x",
+ "react-redux": "5.0.x",
+ "redux": "3.6.x",
+ "url-search-params-polyfill": "1.2.0",
+ "ag-grid": "10.1.x",
+ "ag-grid-enterprise": "10.1.x",
+ "ag-grid-react": "10.1.x"
+ }
}
diff --git a/src-full-width/index.js b/src-full-width/index.js
deleted file mode 100644
index 2c44f4d..0000000
--- a/src-full-width/index.js
+++ /dev/null
@@ -1,18 +0,0 @@
-'use strict';
-
-import ReactDOM from 'react-dom';
-import React from 'react';
-import MyApp from './myApp.jsx';
-// is there a better way of doing this?
-import 'ag-grid-root/dist/styles/ag-grid.css';
-import 'ag-grid-root/dist/styles/theme-fresh.css';
-
-// waiting for dom to load before booting react. we could alternatively
-// put the index.js reference at the end fo the index.html, but i prefer this way.
-document.addEventListener('DOMContentLoaded', ()=> {
- var container = document.getElementById('myAppContainer');
- ReactDOM.render(
- React.createElement(MyApp),
- container
- );
-});
diff --git a/src-full-width/myApp.css b/src-full-width/myApp.css
deleted file mode 100644
index 5e7e6f8..0000000
--- a/src-full-width/myApp.css
+++ /dev/null
@@ -1,28 +0,0 @@
-
-.ag-cell {
- padding-top: 2px !important;
- padding-bottom: 2px !important;
-}
-
-label {
- font-weight: normal !important;
-}
-
-.div-percent-bar {
- display: inline-block;
- height: 20px;
- position: relative;
-}
-
-.div-percent-value {
- position: absolute;
- padding-left: 4px;
- font-weight: bold;
- font-size: 13px;
-}
-
-.div-outer-div {
- display: inline-block;
- height: 100%;
- width: 100%;
-}
diff --git a/src-full-width/myApp.jsx b/src-full-width/myApp.jsx
deleted file mode 100644
index e97ac25..0000000
--- a/src-full-width/myApp.jsx
+++ /dev/null
@@ -1,356 +0,0 @@
-import React from "react";
-import {AgGridReact} from "ag-grid-react";
-import "./myApp.css";
-import "ag-grid-enterprise";
-
-
-export class DetailPanelCellRenderer extends React.Component{
-
-
-
- render (){
- return
-
-
Name: +parentRecord.name+
-
Account: +parentRecord.account+
-
-
-
-
;
- }
-}
-
-
-
-// take this line out if you do not want to use ag-Grid-Enterprise
-// a list of names we pick from when generating data
-var firstnames = ['Sophia','Emma','Olivia','Isabella','Mia','Ava','Lily','Zoe','Emily','Chloe','Layla','Madison','Madelyn','Abigail','Aubrey','Charlotte','Amelia','Ella','Kaylee','Avery','Aaliyah','Hailey','Hannah','Addison','Riley','Harper','Aria','Arianna','Mackenzie','Lila','Evelyn','Adalyn','Grace','Brooklyn','Ellie','Anna','Kaitlyn','Isabelle','Sophie','Scarlett','Natalie','Leah','Sarah','Nora','Mila','Elizabeth','Lillian','Kylie','Audrey','Lucy','Maya'];
-var lastnames = ['Smith','Jones','Williams','Taylor','Brown','Davies','Evans','Wilson','Thomas','Johnson'];
-
-var images = ['niall','sean','alberto','statue','horse'];
-// each call gets a unique id, nothing to do with the grid, just help make the sample
-// data more realistic
-var callIdSequence = 555;
-
-// method creates all the data, both the top level grid and the lower level grids
-function createRowData() {
- var rowData = [];
-
- for (var i = 0; i < 20; i++) {
- var firstName = firstnames[Math.floor(Math.random()*firstnames.length)];
- var lastName = lastnames[Math.floor(Math.random()*lastnames.length)];
-
- var image = images[i % images.length];
-
- var totalDuration = 0;
-
- var callRecords = [];
- // call count is random number between 20 and 120
- var callCount = Math.floor(Math.random() * 100) + 20;
- for (var j = 0; j.5) ? 'In' : 'Out',
- // made up number
- number: '(0' + Math.floor(Math.random() * 10) + ') ' + Math.floor(Math.random() * 100000000)
- };
- callRecords.push(callRecord);
- totalDuration += callDuration;
- }
-
- var record = {
- name: firstName + ' ' + lastName,
- account: i + 177000,
- totalCalls: callCount,
- image: image,
- // convert from seconds to minutes
- totalMinutes: totalDuration / 60,
- callRecords: callRecords
- };
- rowData.push(record);
- }
-
- return rowData;
-}
-
-var minuteCellFormatter = function (params) {
- return params.value.toLocaleString() + 'm';
-};
-
-var secondCellFormatter= function (params) {
- return params.value.toLocaleString() + 's';
-};
-
-var masterColumnDefs = [
- {headerName: 'Name', field: 'name',
- // left column is going to act as group column, with the expand / contract controls
- cellRenderer: 'group',
- // we don't want the child count - it would be one each time anyway as each parent
- // not has exactly one child node
- cellRendererParams: { suppressCount: true }
- },
- {headerName: 'Account', field: 'account'},
- {headerName: 'Calls', field: 'totalCalls'},
- {headerName: 'Minutes', field: 'totalMinutes', cellFormatter: minuteCellFormatter}
-];
-
-var detailColumnDefs = [
- {headerName: 'Call ID', field: 'callId', cellClass: 'call-record-cell'},
- {headerName: 'Direction', field: 'direction', cellClass: 'call-record-cell'},
- {headerName: 'Number', field: 'number', cellClass: 'call-record-cell'},
- {headerName: 'Duration', field: 'duration', cellClass: 'call-record-cell', cellFormatter: secondCellFormatter},
- {headerName: 'Switch', field: 'switchCode', cellClass: 'call-record-cell'}
-];
-
-var rowData = createRowData();
-export default class MyApp extends React.Component {
-
- constructor() {
- super();
-
- this.state = {
- quickFilterText: null,
- showGrid: true,
- showToolPanel: false,
- icons: {
- columnRemoveFromGroup: '',
- filter: '',
- sortAscending: '',
- sortDescending: '',
- groupExpanded: '',
- groupContracted: '',
- columnGroupOpened: '',
- columnGroupClosed: ''
- }
- };
-
- // the grid options are optional, because you can provide every property
- // to the grid via standard React properties. however, the react interface
- // doesn't block you from using the standard JavaScript interface if you
- // wish. Maybe you have the gridOptions stored as JSON on your server? If
- // you do, the providing the gridOptions as a standalone object is just
- // what you want!
- this.gridOptions = {
- columnDefs: masterColumnDefs,
- rowData: rowData,
- //We register the react date component that ag-grid will use to render
- // this is how you listen for events using gridOptions
- onModelUpdated: function () {
- console.log('event onModelUpdated received');
- },
- defaultColDef : {
- headerComponentParams : {
- menuIcon: 'fa-bars'
- }
- },
- // this is a simple property
- rowBuffer: 10 // no need to set this, the default is fine for almost all scenarios
- };
- }
-
- onShowGrid(show) {
- this.setState({
- showGrid: show
- });
- }
-
- onToggleToolPanel(event) {
- this.setState({showToolPanel: event.target.checked});
- }
-
- onGridReady(params) {
- this.api = params.api;
- this.columnApi = params.columnApi;
- params.api.sizeColumnsToFit();
- }
-
- selectAll() {
- this.api.selectAll();
- }
-
- deselectAll() {
- this.api.deselectAll();
- }
-
- setCountryVisible(visible) {
- this.columnApi.setColumnVisible('country', visible);
- }
-
- onQuickFilterText(event) {
- this.setState({quickFilterText: event.target.value});
- }
-
- onCellClicked(event) {
- console.log('onCellClicked: ' + event.data.name + ', col ' + event.colIndex);
- }
-
- onRowSelected(event) {
- console.log('onRowSelected: ' + event.node.data.name);
- }
-
- onRefreshData() {
- var newRowData = new RowDataFactory().createRowData();
- this.setState({
- rowData: newRowData
- });
- }
-
- invokeSkillsFilterMethod() {
- var skillsFilter = this.api.getFilterInstance('skills');
- var componentInstance = skillsFilter.getFrameworkComponentInstance();
- componentInstance.helloFromSkillsFilter();
- }
-
- dobFilter () {
- let dateFilterComponent = this.gridOptions.api.getFilterInstance('dob');
- dateFilterComponent.setFilterType('equals');
- dateFilterComponent.setDateFrom('2000-01-01');
- this.gridOptions.api.onFilterChanged();
-
- }
-
- onIsFullWidthCell (rowNode) {
- return rowNode.level === 1;
- }
-
- onGetRowHeight (params) {
- var rowIsDetailRow = params.node.level===1;
- // return 100 when detail row, otherwise return 25
- return rowIsDetailRow ? 75 : 25;
- }
-
- onGetNodeChildDetails (record) {
- if (record.callRecords) {
- return {
- group: true,
- // the key is used by the default group cellRenderer
- key: record.name,
- // provide ag-Grid with the children of this group
- children: [record.callRecords],
- // for demo, expand the third row by default
- expanded: record.account === 177005
- };
- } else {
- return null;
- }
- }
-
- render() {
- var gridTemplate;
- var bottomHeaderTemplate;
- var topHeaderTemplate;
-
- topHeaderTemplate = (
-
-
-
-
-
-
-
- Employees Skills and Contact Details
-
-
- );
-
- // showing the bottom header and grid is optional, so we put in a switch
- if (this.state.showGrid) {
- bottomHeaderTemplate = (
-
-
-
- Grid API:
-
-
-
-
- Column API:
-
-
-
-
-
-
-
-
-
-
-
- Filter API:
-
-
-
-
-
-
- );
- gridTemplate = (
-
- );
- }
-
- return
-
- {topHeaderTemplate}
- {bottomHeaderTemplate}
- {gridTemplate}
-
-
;
- }
-
-}
\ No newline at end of file
diff --git a/src-grouped/ColDefFactory.jsx b/src-grouped/ColDefFactory.jsx
deleted file mode 100644
index b53d820..0000000
--- a/src-grouped/ColDefFactory.jsx
+++ /dev/null
@@ -1,17 +0,0 @@
-export default class ColDefFactory {
- createColDefs() {
- return [
- {headerName: "Athlete", field: "athlete", width: 200},
- {headerName: "Age", field: "age", width: 90},
- {headerName: "Gold", field: "gold", width: 100, aggFunc: 'sum'},
- {headerName: "Silver", field: "silver", width: 100, aggFunc: 'sum'},
- {headerName: "Bronze", field: "bronze", width: 100, aggFunc: 'sum'},
- {headerName: "Total", field: "total", width: 100, aggFunc: 'sum'},
- {headerName: "Country", field: "country", width: 120, rowGroupIndex: 0},
- {headerName: "Year", field: "year", width: 90},
- {headerName: "Date", field: "date", width: 110},
- {headerName: "Sport", field: "sport", width: 110}
- ];
- }
-}
-
diff --git a/src-grouped/index.js b/src-grouped/index.js
deleted file mode 100644
index 2c44f4d..0000000
--- a/src-grouped/index.js
+++ /dev/null
@@ -1,18 +0,0 @@
-'use strict';
-
-import ReactDOM from 'react-dom';
-import React from 'react';
-import MyApp from './myApp.jsx';
-// is there a better way of doing this?
-import 'ag-grid-root/dist/styles/ag-grid.css';
-import 'ag-grid-root/dist/styles/theme-fresh.css';
-
-// waiting for dom to load before booting react. we could alternatively
-// put the index.js reference at the end fo the index.html, but i prefer this way.
-document.addEventListener('DOMContentLoaded', ()=> {
- var container = document.getElementById('myAppContainer');
- ReactDOM.render(
- React.createElement(MyApp),
- container
- );
-});
diff --git a/src-grouped/myApp.jsx b/src-grouped/myApp.jsx
deleted file mode 100644
index 5fc4c6a..0000000
--- a/src-grouped/myApp.jsx
+++ /dev/null
@@ -1,99 +0,0 @@
-import React from "react";
-import {AgGridReact} from "ag-grid-react";
-import ColDefFactory from "./ColDefFactory.jsx";
-import "./myApp.css";
-
-// take this line out if you do not want to use ag-Grid-Enterprise
-import "ag-grid-enterprise";
-
-
-export default class MyApp extends React.Component {
-
- constructor() {
- super();
-
- this.state = {
- showToolPanel: false,
- columnDefs: new ColDefFactory().createColDefs(),
- rowData: null,
- };
-
- this.gridOptions = {
- groupRowInnerRenderer: function (params) {
- var FLAG_CODES = {
- 'Ireland': 'ie',
- 'United States': 'us',
- 'Russia': 'ru',
- 'Australia': 'au',
- 'Canada': 'ca',
- 'Norway': 'no',
- 'China': 'cn',
- 'Zimbabwe': 'zw',
- 'Netherlands': 'nl',
- 'South Korea': 'kr',
- 'Croatia': 'hr',
- 'France': 'fr'
- };
-
- var flagCode = FLAG_CODES[params.node.key];
-
- var html = '';
- if (flagCode) {
- html += '
'
- }
-
- html += ' COUNTRY_NAME'.replace('COUNTRY_NAME', params.node.key);
- html += ' Gold: GOLD_COUNT'.replace('GOLD_COUNT', params.data.gold);
- html += ' Silver: SILVER_COUNT'.replace('SILVER_COUNT', params.data.silver);
- html += ' Bronze: BRONZE_COUNT'.replace('BRONZE_COUNT', params.data.bronze);
-
- return html;
- }
- };
- }
-
- onGridReady(params) {
- this.api = params.api;
- this.columnApi = params.columnApi;
-
- var that = this;
- var httpRequest = new XMLHttpRequest();
- httpRequest.open('GET', '/olympicWinners.json');
- httpRequest.send();
- httpRequest.onreadystatechange = function() {
- if (httpRequest.readyState == 4 && httpRequest.status == 200) {
- var httpResult = JSON.parse(httpRequest.responseText);
- that.api.setRowData(httpResult);
- }
- };
-
- }
-
- render() {
- var gridTemplate = (
-
- );
-
- return ;
- }
-
-}
diff --git a/src-standard/index.js b/src-standard/index.js
deleted file mode 100644
index 001f895..0000000
--- a/src-standard/index.js
+++ /dev/null
@@ -1,18 +0,0 @@
-'use strict';
-
-import ReactDOM from 'react-dom';
-import React from 'react';
-import MyApp from './myApp.jsx';
-
-import 'ag-grid-root/dist/styles/ag-grid.css';
-import 'ag-grid-root/dist/styles/theme-fresh.css';
-
-// waiting for dom to load before booting react. we could alternatively
-// put the index.js reference at the end fo the index.html, but i prefer this way.
-document.addEventListener('DOMContentLoaded', ()=> {
- var container = document.getElementById('myAppContainer');
- ReactDOM.render(
- React.createElement(MyApp),
- container
- );
-});
diff --git a/src-standard/myApp.css b/src-standard/myApp.css
deleted file mode 100644
index 5e7e6f8..0000000
--- a/src-standard/myApp.css
+++ /dev/null
@@ -1,28 +0,0 @@
-
-.ag-cell {
- padding-top: 2px !important;
- padding-bottom: 2px !important;
-}
-
-label {
- font-weight: normal !important;
-}
-
-.div-percent-bar {
- display: inline-block;
- height: 20px;
- position: relative;
-}
-
-.div-percent-value {
- position: absolute;
- padding-left: 4px;
- font-weight: bold;
- font-size: 13px;
-}
-
-.div-outer-div {
- display: inline-block;
- height: 100%;
- width: 100%;
-}
diff --git a/src-trader-dashboard/actions/fxDataActions.js b/src-trader-dashboard/actions/fxDataActions.js
new file mode 100644
index 0000000..4023207
--- /dev/null
+++ b/src-trader-dashboard/actions/fxDataActions.js
@@ -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)
+ };
+}
+
diff --git a/src-trader-dashboard/components/ControlPanel.jsx b/src-trader-dashboard/components/ControlPanel.jsx
index 2f01567..127c8aa 100644
--- a/src-trader-dashboard/components/ControlPanel.jsx
+++ b/src-trader-dashboard/components/ControlPanel.jsx
@@ -1,4 +1,4 @@
-import React, { Component } from 'react';
+import React, {Component} from "react";
export default class extends Component {
constructor(props) {
diff --git a/src-trader-dashboard/components/FxPanel.jsx b/src-trader-dashboard/components/FxPanel.jsx
new file mode 100644
index 0000000..db3582b
--- /dev/null
+++ b/src-trader-dashboard/components/FxPanel.jsx
@@ -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 (
+
+ );
+ }
+};
diff --git a/src-trader-dashboard/components/FxQuoteMatrix.jsx b/src-trader-dashboard/components/FxQuoteMatrix.jsx
index a95e8d0..74e684e 100644
--- a/src-trader-dashboard/components/FxQuoteMatrix.jsx
+++ b/src-trader-dashboard/components/FxQuoteMatrix.jsx
@@ -1,18 +1,17 @@
import React, {Component} from "react";
import {connect} from "react-redux";
+
import {AgGridReact} from "ag-grid-react";
-import ExchangeService from "../services/ExchangeService.jsx";
+import assign from "lodash/assign";
+import uniq from "lodash/uniq";
class FxQuoteMatrix extends Component {
constructor(props) {
super(props);
- this.exchangeService = new ExchangeService();
-
this.state = {
- columnDefs: this.exchangeService.getFxMatrixHeaderNames(),
- // rowData: this.exchangeService.getFxMatrixSnapshot()
+ columnDefs: this.props.fxDataService.getFxMatrixHeaderNames()
};
// grid events
@@ -22,58 +21,49 @@ class FxQuoteMatrix extends Component {
onGridReady(params) {
this.gridApi = params.api;
this.columnApi = params.columnApi;
+
+ if (this.props.rowData) {
+ this.gridApi.setRowData(this.props.rowData)
+ }
}
-
- // componentWillUnmount() {
- // this.exchangeService.removeSubscribers();
- // }
-
componentWillReceiveProps(nextProps) {
- console.log(props);
- // if (nextProps.selectedExchange.supportedStocks !== this.props.selectedExchange.supportedStocks) {
- // if (!this.gridApi) {
- // return;
- // }
- //
- // const currentSymbols = this.props.selectedExchange.supportedStocks;
- // const nextSymbols = nextProps.selectedExchange.supportedStocks;
- //
- // // Unsubscribe to current ones that will be removed
- // const symbolsRemoved = difference(currentSymbols, nextSymbols);
- // forEach(symbolsRemoved, symbol => {
- // this.exchangeService.removeSubscriber(this.updateQuote, symbol);
- // });
- //
- // // Subscribe to new ones that need to be added
- // const symbolsAdded = difference(nextSymbols, currentSymbols);
- // forEach(symbolsAdded, symbol => {
- // this.exchangeService.addSubscriber(this.updateQuote, symbol);
- // });
- //
- // // Remove ag-grid nodes as necessary
- // const nodesToRemove = [];
- // this.gridApi.forEachNode(node => {
- // const {data} = node;
- // if (includes(symbolsRemoved, data.symbol)) {
- // nodesToRemove.push(node);
- // }
- // });
- // this.gridApi.removeItems(nodesToRemove);
- //
- // // Insert new ag-grid nodes as necessary
- // this.gridApi.addItems(map(symbolsAdded, symbol => this.exchangeService.getTicker(symbol)));
- // }
+ const newRowData = nextProps.rowData;
+
+ 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];
+
+ 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);
+ }
+ }
+
+ this.gridApi.refreshCells(updatedNodes, uniq(updatedCols));
}
render() {
return (
-
{
return {
- rowData: state.fxRowData
+ rowData: state.fxData
}
}
)(FxQuoteMatrix);
\ No newline at end of file
diff --git a/src-trader-dashboard/components/PriceChangesGrid.jsx b/src-trader-dashboard/components/PriceChangesGrid.jsx
index 257b2ac..9daf03f 100644
--- a/src-trader-dashboard/components/PriceChangesGrid.jsx
+++ b/src-trader-dashboard/components/PriceChangesGrid.jsx
@@ -1,8 +1,5 @@
import React, {Component} from "react";
-// take this line out if you do not want to use ag-Grid-Enterprise
-import "ag-grid-enterprise";
-
import {AgGridReact} from "ag-grid-react";
import map from "lodash/map";
@@ -44,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
}
]
};
@@ -61,10 +49,10 @@ export default class extends Component {
// grid events
this.onGridReady = this.onGridReady.bind(this);
- this.onRowClicked = this.onRowClicked.bind(this);
+ this.onSelectionChanged = this.onSelectionChanged.bind(this);
// component events
- this.updateQuote = this.updateQuote.bind(this);
+ this.updateSymbol = this.updateSymbol.bind(this);
}
numberFormatter(params) {
@@ -83,16 +71,20 @@ export default class extends Component {
let rowData = map(this.props.selectedExchange.supportedStocks, symbol => this.exchangeService.getTicker(symbol));
this.gridApi.addItems(rowData);
+ // select the first symbol to show the chart
+ this.gridApi.getModel().getRow(0).setSelected(true);
+
this.gridApi.sizeColumnsToFit();
}
- onRowClicked(params) {
- this.props.onRowClicked(params.data.symbol);
+ onSelectionChanged() {
+ let selectedNode = this.gridApi.getSelectedNodes()[0];
+ this.props.onSelectionChanged(selectedNode ? selectedNode.data.symbol : null);
}
componentWillMount() {
this.props.selectedExchange.supportedStocks.forEach(symbol => {
- this.exchangeService.addSubscriber(this.updateQuote, symbol);
+ this.exchangeService.addSubscriber(this.updateSymbol, symbol);
});
}
@@ -105,6 +97,7 @@ export default class extends Component {
if (!this.gridApi) {
return;
}
+ this.gridApi.deselectAll();
const currentSymbols = this.props.selectedExchange.supportedStocks;
const nextSymbols = nextProps.selectedExchange.supportedStocks;
@@ -112,13 +105,13 @@ export default class extends Component {
// Unsubscribe to current ones that will be removed
const symbolsRemoved = difference(currentSymbols, nextSymbols);
forEach(symbolsRemoved, symbol => {
- this.exchangeService.removeSubscriber(this.updateQuote, symbol);
+ this.exchangeService.removeSubscriber(this.updateSymbol, symbol);
});
// Subscribe to new ones that need to be added
const symbolsAdded = difference(nextSymbols, currentSymbols);
forEach(symbolsAdded, symbol => {
- this.exchangeService.addSubscriber(this.updateQuote, symbol);
+ this.exchangeService.addSubscriber(this.updateSymbol, symbol);
});
// Remove ag-grid nodes as necessary
@@ -132,11 +125,15 @@ export default class extends Component {
this.gridApi.removeItems(nodesToRemove);
// Insert new ag-grid nodes as necessary
- this.gridApi.addItems(map(symbolsAdded, symbol => this.exchangeService.getTicker(symbol)));
+ let rowData = map(symbolsAdded, symbol => this.exchangeService.getTicker(symbol));
+ this.gridApi.addItems(rowData);
+
+ // select the first symbol to show the chart
+ this.gridApi.getModel().getRow(0).setSelected(true);
}
}
- updateQuote(quote) {
+ updateSymbol(symbol) {
if (!this.gridApi) {
// the grid isn't ready yet
return;
@@ -147,13 +144,13 @@ export default class extends Component {
this.gridApi.forEachNode(node => {
const {data} = node;
- if (data.symbol === quote.symbol) {
+ if (data.symbol === symbol.symbol) {
for (const def of this.state.columnDefs) {
- if (data[def.field] !== quote[def.field]) {
+ if (data[def.field] !== symbol[def.field]) {
updatedCols.push(def.field);
}
}
- assign(data, quote);
+ assign(data, symbol);
updatedNodes.push(node);
}
});
@@ -163,16 +160,17 @@ export default class extends Component {
render() {
return (
-
+ onSelectionChanged={this.onSelectionChanged}>
);
diff --git a/src-trader-dashboard/components/StockDetailPanel.jsx b/src-trader-dashboard/components/StockDetailPanel.jsx
index 76d4dda..b008e73 100644
--- a/src-trader-dashboard/components/StockDetailPanel.jsx
+++ b/src-trader-dashboard/components/StockDetailPanel.jsx
@@ -21,7 +21,7 @@ export default class extends Component {
}
}
- componentWillReceiveProps(nextProps, nextState) {
+ componentWillReceiveProps(nextProps) {
if (nextProps.selectedSymbol &&
nextProps.selectedSymbol !== this.props.selectedSymbol) {
let stockDetail = this.exchangeService.getTickerDetail(nextProps.selectedSymbol);
@@ -35,7 +35,7 @@ export default class extends Component {
}
}
- shouldComponentUpdate(nextProps, nextState) {
+ shouldComponentUpdate(nextProps) {
return nextProps.selectedSymbol !== this.props.selectedSymbol;
}
diff --git a/src-trader-dashboard/components/StockHistoricalChartPanel.jsx b/src-trader-dashboard/components/StockHistoricalChartPanel.jsx
index 25befb7..ae78563 100644
--- a/src-trader-dashboard/components/StockHistoricalChartPanel.jsx
+++ b/src-trader-dashboard/components/StockHistoricalChartPanel.jsx
@@ -29,7 +29,7 @@ export default class extends Component {
static get defaultProps() {
return {
- graphHeight: 320,
+ graphHeight: 230,
graphWidth: 400
}
}
diff --git a/src-trader-dashboard/components/StockPanel.jsx b/src-trader-dashboard/components/StockPanel.jsx
index 6f2149c..0ab7b67 100644
--- a/src-trader-dashboard/components/StockPanel.jsx
+++ b/src-trader-dashboard/components/StockPanel.jsx
@@ -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) {
@@ -12,7 +12,7 @@ export default class extends Component {
selectedSymbol: null
};
- this.onRowClicked = this.onRowClicked.bind(this);
+ this.onSelectionChanged = this.onSelectionChanged.bind(this);
}
componentWillReceiveProps(nextProps) {
@@ -28,7 +28,7 @@ export default class extends Component {
nextState.selectedSymbol !== this.state.selectedSymbol;
}
- onRowClicked(selectedSymbol) {
+ onSelectionChanged(selectedSymbol) {
this.setState({
selectedSymbol
})
@@ -37,18 +37,18 @@ export default class extends Component {
render() {
return (
- {/*
*/}
- {/*
*/}
- {/*
*/}
- {/**/}
- {/*
*/}
- {/*
*/}
+
-
+
);
diff --git a/src-trader-dashboard/components/StockPriceDeltaPanel.jsx b/src-trader-dashboard/components/StockPriceDeltaPanel.jsx
index a41f998..62d0bf3 100644
--- a/src-trader-dashboard/components/StockPriceDeltaPanel.jsx
+++ b/src-trader-dashboard/components/StockPriceDeltaPanel.jsx
@@ -18,7 +18,7 @@ export default class extends Component {
this.updatePriceDelta(this.props.pricingDelta);
}
- componentWillReceiveProps(nextProps, nextState) {
+ componentWillReceiveProps(nextProps) {
if (!isEqual(this.props.pricingDelta, nextProps.pricingDelta)) {
this.updatePriceDelta(nextProps.pricingDelta);
}
@@ -73,7 +73,7 @@ export default class extends Component {
let swingStyle = this.state.delta >= 0 ? positiveSwingStyle : negativeSwingStyle;
- if(!this.props.pricingDelta) {
+ if (!this.props.pricingDelta) {
return null;
} else {
return (
diff --git a/src-trader-dashboard/components/TopMoversGrid.jsx b/src-trader-dashboard/components/TopMoversGrid.jsx
new file mode 100644
index 0000000..1e6fa96
--- /dev/null
+++ b/src-trader-dashboard/components/TopMoversGrid.jsx
@@ -0,0 +1,88 @@
+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',
+ sort: 'desc',
+ cellFormatter(params) {
+ return params.value.toFixed(2)
+ }
+ },
+ ]
+ };
+
+ // grid events
+ this.onGridReady = this.onGridReady.bind(this);
+ this.getRowNodeId = this.getRowNodeId.bind(this);
+ }
+
+ getRowNodeId(data) {
+ return data.symbol;
+ }
+
+ onGridReady(params) {
+ this.gridApi = params.api;
+ this.columnApi = params.columnApi;
+
+ this.gridApi.sizeColumnsToFit();
+ }
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+export default connect(
+ (state) => {
+ return {
+ rowData: state.fxTopMovers
+ }
+ }
+)(TopMoversGrid);
\ No newline at end of file
diff --git a/src-trader-dashboard/components/renderers/HorizontalBarComponent.jsx b/src-trader-dashboard/components/renderers/HorizontalBarComponent.jsx
new file mode 100644
index 0000000..8111b1e
--- /dev/null
+++ b/src-trader-dashboard/components/renderers/HorizontalBarComponent.jsx
@@ -0,0 +1,42 @@
+import React, {Component} from "react";
+
+export default class HorizontalBarComponent extends Component {
+
+ render() {
+ let positiveChange = {
+ fill: "green"
+ };
+
+ let negativeChange = {
+ fill: "red"
+ };
+
+ let text = {
+ position: "absolute",
+ top: 0,
+ width: "100%",
+ textAlign: "right"
+ };
+
+ let pctNetChange = this.props.value;
+ let pctNetChangeBar = Math.min(Math.abs(pctNetChange) * 100, 100) / 2;
+
+ let barWidth = `${pctNetChangeBar}%`;
+ let barStyle = pctNetChange >= 0 ? positiveChange : negativeChange;
+ let barX = `${pctNetChange >= 0 ? '50' : 50 - pctNetChangeBar}%`;
+ return (
+
+
+
+
+
{pctNetChange}
+
+ )
+ }
+}
+
+HorizontalBarComponent.propTypes = {
+ params: React.PropTypes.object
+};
\ No newline at end of file
diff --git a/src-trader-dashboard/index.html b/src-trader-dashboard/index.html
index f69d9b6..844ccd4 100644
--- a/src-trader-dashboard/index.html
+++ b/src-trader-dashboard/index.html
@@ -3,13 +3,17 @@
-
+
+
+
+
+
+
+