Compare commits
77 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a45846cb7 | ||
|
|
2d8d5b125e | ||
|
|
818b7798ee | ||
|
|
15ba8d7e60 | ||
|
|
eab958c3e9 | ||
|
|
3a17b7f638 | ||
|
|
734d39e2d7 | ||
|
|
0d7cace354 | ||
|
|
fa0881bfcf | ||
|
|
62b9e3582a | ||
|
|
949797d4be | ||
|
|
a869fe3819 | ||
|
|
c611af4f39 | ||
|
|
7cab3190ae | ||
|
|
71fe653fcb | ||
|
|
ba1c213ce0 | ||
|
|
46217c9ad7 | ||
|
|
017642e4d7 | ||
|
|
79b0eda61b | ||
|
|
a70f248af5 | ||
|
|
32142b435b | ||
|
|
d71781899d | ||
|
|
9560f86bf5 | ||
|
|
9f4b270884 | ||
|
|
e5627d8b9f | ||
|
|
f8f0c21af2 | ||
|
|
b54d549959 | ||
|
|
464a0ea9f4 | ||
|
|
675cbe497c | ||
|
|
a4dbbf529d | ||
|
|
89d36b0bf4 | ||
|
|
9ff1a54e57 | ||
|
|
7ed2666aaf | ||
|
|
76258a8ee4 | ||
|
|
7564f1597a | ||
|
|
5f42824c5e | ||
|
|
ef44ebc166 | ||
|
|
9c3c7a77c0 | ||
|
|
f8b8a0b066 | ||
|
|
e1871affe3 | ||
|
|
3226c0d6ba | ||
|
|
1b3fc21107 | ||
|
|
587af2e888 | ||
|
|
4ef1959a52 | ||
|
|
ddefba039a | ||
|
|
06ebb8bf18 | ||
|
|
e183a0503a | ||
|
|
19771949cb | ||
|
|
5fbdb690af | ||
|
|
93fd0fe112 | ||
|
|
a8163cad16 | ||
|
|
3f54c23b4d | ||
|
|
00dd86f4c8 | ||
|
|
e8ba0413ae | ||
|
|
a3a0336f6b | ||
|
|
a438cb6169 | ||
|
|
8677118a27 | ||
|
|
4069f6bc10 | ||
|
|
575edb5b85 | ||
|
|
042dfc62a6 | ||
|
|
d59b5e8071 | ||
|
|
afa03227e4 | ||
|
|
a2952230d0 | ||
|
|
7f8d1bb3a7 | ||
|
|
801bf16339 | ||
|
|
cf1be920ee | ||
|
|
f8ad31c2e0 | ||
|
|
e026fb65e3 | ||
|
|
05a7371467 | ||
|
|
4ea0b7f8b2 | ||
|
|
5904f47d55 | ||
|
|
25b416b71d | ||
|
|
abb7828921 | ||
|
|
ad5fd2d1ce | ||
|
|
6d686f1c02 | ||
|
|
6376a24e3a | ||
|
|
9c8cf08d2a |
13
README.md
@@ -4,7 +4,14 @@ ag-Grid React Example
|
||||
|
||||
Examples of running ag-Grid inside React application.
|
||||
|
||||
See the [www.ag-grid.com](http://www.ag-grid.com).
|
||||
See [www.ag-grid.com](http://www.ag-grid.com) for an overview and full documentation.
|
||||
|
||||
Frameworks Supported
|
||||
====================
|
||||
Framework specific Getting Started guides:
|
||||
[Angular 1](https://www.ag-grid.com/best-angularjs-data-grid/) | [Angular 2](https://www.ag-grid.com/best-angular-2-data-grid/) | [Aurelia](https://www.ag-grid.com/best-aurelia-data-grid/)
|
||||
[Javascript](https://www.ag-grid.com/best-javascript-data-grid/) | [React](https://www.ag-grid.com/best-react-data-grid/) | [TypeScript](https://www.ag-grid.com/ag-grid-typescript-webpack-2/)
|
||||
[VueJS](https://www.ag-grid.com/best-vuejs-data-grid/) | [Web Components](https://www.ag-grid.com/best-web-component-data-grid/)
|
||||
|
||||
There are two examples:
|
||||
|
||||
@@ -18,5 +25,5 @@ Building
|
||||
To build:
|
||||
- `npm install`
|
||||
- `npm install webpack -g`
|
||||
- `npm run standard` or `npm run large`
|
||||
- navigate to localhost:8080
|
||||
- `npm run example`, `npm run large` or `npm run trader`
|
||||
- navigate to localhost:8080
|
||||
26
gulpfile.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const gulp = require('gulp');
|
||||
const gulpTypescript = require('gulp-typescript');
|
||||
const merge = require('merge2');
|
||||
|
||||
gulp.task('watch', watchTask);
|
||||
gulp.task('rebuild-ag-grid-react', rebuildAgGridReact);
|
||||
|
||||
const tsConfig = '../ag-grid-react/tsconfig.json';
|
||||
const tsProject = gulpTypescript.createProject(tsConfig);
|
||||
|
||||
function rebuildAgGridReact() {
|
||||
const tsResult = gulp
|
||||
.src('../ag-grid-react/src/**/*.ts')
|
||||
.pipe(tsProject());
|
||||
|
||||
return merge([
|
||||
tsResult.dts
|
||||
.pipe(gulp.dest('node_modules/ag-grid-react/lib')),
|
||||
tsResult.js
|
||||
.pipe(gulp.dest('node_modules/ag-grid-react/lib'))
|
||||
]);
|
||||
}
|
||||
|
||||
function watchTask() {
|
||||
gulp.watch(['../ag-grid-react/src/**/*'], rebuildAgGridReact);
|
||||
}
|
||||
BIN
images/alberto.png
Normal file
|
After Width: | Height: | Size: 69 KiB |
BIN
images/favicon.ico
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
images/fire.png
Normal file
|
After Width: | Height: | Size: 606 B |
BIN
images/flags/ar.png
Normal file
|
After Width: | Height: | Size: 128 B |
BIN
images/flags/br.png
Normal file
|
After Width: | Height: | Size: 256 B |
BIN
images/flags/co.png
Normal file
|
After Width: | Height: | Size: 99 B |
BIN
images/flags/de.png
Normal file
|
After Width: | Height: | Size: 99 B |
BIN
images/flags/es.png
Normal file
|
After Width: | Height: | Size: 174 B |
BIN
images/flags/fr.png
Normal file
|
After Width: | Height: | Size: 94 B |
BIN
images/flags/gb.png
Normal file
|
After Width: | Height: | Size: 289 B |
BIN
images/flags/gr.png
Normal file
|
After Width: | Height: | Size: 228 B |
BIN
images/flags/ie.png
Normal file
|
After Width: | Height: | Size: 119 B |
BIN
images/flags/is.png
Normal file
|
After Width: | Height: | Size: 154 B |
BIN
images/flags/it.png
Normal file
|
After Width: | Height: | Size: 94 B |
BIN
images/flags/mt.png
Normal file
|
After Width: | Height: | Size: 136 B |
BIN
images/flags/no.png
Normal file
|
After Width: | Height: | Size: 154 B |
BIN
images/flags/pe.png
Normal file
|
After Width: | Height: | Size: 89 B |
BIN
images/flags/pt.png
Normal file
|
After Width: | Height: | Size: 227 B |
BIN
images/flags/se.png
Normal file
|
After Width: | Height: | Size: 97 B |
BIN
images/flags/uy.png
Normal file
|
After Width: | Height: | Size: 222 B |
BIN
images/flags/ve.png
Normal file
|
After Width: | Height: | Size: 173 B |
BIN
images/frost.png
Normal file
|
After Width: | Height: | Size: 739 B |
BIN
images/horse.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
images/niall.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
images/phone.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
images/sean.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
images/smiley-sad.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
images/smiley.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
images/statue.png
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
images/sun.png
Normal file
|
After Width: | Height: | Size: 570 B |
99
index.html
@@ -16,6 +16,105 @@
|
||||
margin-left: 4px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.customHeaderMenuButton{
|
||||
margin-top: 5px;
|
||||
margin-left: 4px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.customHeaderLabel{
|
||||
margin-left: 5px;
|
||||
margin-top: 3px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.customSortDownLabel{
|
||||
float: left;
|
||||
margin-left: 10px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.customSortUpLabel{
|
||||
float: left;
|
||||
margin-left: 3px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.customSortRemoveLabel{
|
||||
float: left;
|
||||
font-size: 11px;
|
||||
margin-left: 3px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.active {
|
||||
color: cornflowerblue;
|
||||
}
|
||||
|
||||
.hidden { display:none; }
|
||||
|
||||
|
||||
.customHeaderLabel{
|
||||
margin-left: 5px;
|
||||
margin-top: 3px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.customExpandButton{
|
||||
float:right;
|
||||
margin-top: 5px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.expanded {
|
||||
animation-name: toExpanded;
|
||||
animation-duration: 1s;
|
||||
-ms-transform: rotate(180deg); /* IE 9 */
|
||||
-webkit-transform: rotate(180deg); /* Chrome, Safari, Opera */
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.collapsed {
|
||||
color: cornflowerblue;
|
||||
animation-name: toCollapsed;
|
||||
animation-duration: 1s;
|
||||
-ms-transform: rotate(0deg); /* IE 9 */
|
||||
-webkit-transform: rotate(0deg); /* Chrome, Safari, Opera */
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@keyframes toExpanded{
|
||||
from {
|
||||
color: cornflowerblue;
|
||||
-ms-transform: rotate(0deg); /* IE 9 */
|
||||
-webkit-transform: rotate(0deg); /* Chrome, Safari, Opera */
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
color: black;
|
||||
-ms-transform: rotate(180deg); /* IE 9 */
|
||||
-webkit-transform: rotate(180deg); /* Chrome, Safari, Opera */
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes toCollapsed{
|
||||
from {
|
||||
color: black;
|
||||
-ms-transform: rotate(180deg); /* IE 9 */
|
||||
-webkit-transform: rotate(180deg); /* Chrome, Safari, Opera */
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
to {
|
||||
color: cornflowerblue;
|
||||
-ms-transform: rotate(0deg); /* IE 9 */
|
||||
-webkit-transform: rotate(0deg); /* Chrome, Safari, Opera */
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<body>
|
||||
|
||||
8618
olympicWinners.json
Normal file
68
package.json
@@ -1,15 +1,29 @@
|
||||
{
|
||||
"name": "ag-grid-react-example",
|
||||
"version": "7.2.0",
|
||||
"version": "13.0.1",
|
||||
"description": "Example Reach applicaiton using ag-Grid.",
|
||||
"main": "dist/ag-grid-react-example.js",
|
||||
"scripts": {
|
||||
"standard": "webpack-dev-server --config webpack.config.standard.js --progress --colors --hot --inline",
|
||||
"large": "webpack-dev-server --config webpack.config.large.js --progress --colors --hot --inline"
|
||||
"trader": "webpack-dev-server --content-base src-trader-dashboard/ --config webpack.config.trader.js --progress --colors --hot --inline",
|
||||
"examples": "webpack-dev-server --content-base src/ --config webpack.config.examples.js --progress --colors --hot --inline",
|
||||
"large": "webpack-dev-server --config webpack.config.large.js --progress --colors --hot --inline",
|
||||
"clean": "rimraf dist",
|
||||
"mkdirs": "mkdirp dist/trader/dist dist/examples/dist",
|
||||
"copy-examples": "ncp images dist/examples/images && ncp src/index.html dist/examples/index.html && ncp dist/react-examples.js dist/examples/dist/react-examples.js && ncp src dist/examples/src",
|
||||
"copy-trader": "ncp src-trader-dashboard/index.html dist/trader/index.html && ncp dist/react-trader.js dist/trader/dist/react-trader.js",
|
||||
"copy": "npm run copy-examples && npm run copy-trader",
|
||||
"build-large": "webpack --config webpack.config.large.js --progress --profile --bail",
|
||||
"build-examples": "webpack --config webpack.config.examples.js --progress --profile --bail",
|
||||
"build-dashboard": "webpack --config webpack.config.trader.js --progress --profile --bail",
|
||||
"build-all": "npm run build-examples && npm run build-dashboard",
|
||||
"build": "npm run clean && npm run mkdirs && npm run build-all && npm run copy",
|
||||
"copy-to-docs": "ncp dist/examples ../ag-grid-docs/src/framework-examples/react-examples/examples && ncp dist/trader ../ag-grid-docs/src/framework-examples/react-examples/trader",
|
||||
"build-to-docs": "npm run build && npm run copy-to-docs",
|
||||
"start": "npm run examples"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ceolter/ag-grid-react-example.git"
|
||||
"url": "https://github.com/ag-grid/ag-grid-react-example.git"
|
||||
},
|
||||
"keywords": [
|
||||
"react",
|
||||
@@ -20,24 +34,42 @@
|
||||
"author": "Niall Crosby <niall.crosby@gmail.com>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/ceolter/ag-grid-react-example/issues"
|
||||
"url": "https://github.com/ag-grid/ag-grid-react-example/issues"
|
||||
},
|
||||
"homepage": "http://www.ag-grid.com/",
|
||||
"devDependencies": {
|
||||
"babel-loader": "6.2.1",
|
||||
"babel-preset-es2015": "6.3.13",
|
||||
"babel-preset-react": "6.3.13",
|
||||
"babel-core": "^6.0.0",
|
||||
"css-loader": "0.23.1",
|
||||
"style-loader": "0.13.0",
|
||||
"webpack": "1.12.11",
|
||||
"webpack-dev-server": "^1.14.1"
|
||||
"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",
|
||||
"prop-types": "15.5.x",
|
||||
"rimraf": "2.5.x",
|
||||
"style-loader": "0.13.x",
|
||||
"webpack": "1.12.x",
|
||||
"webpack-dev-server": "1.14.x",
|
||||
"gulp": "3.9.x",
|
||||
"gulp-typescript": "3.1.x",
|
||||
"merge2": "1.0.x",
|
||||
"typescript": "2.3.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "0.14.6",
|
||||
"react-dom": "0.14.6",
|
||||
"ag-grid": "7.2.x",
|
||||
"ag-grid-enterprise": "7.2.x",
|
||||
"ag-grid-react": "7.2.x"
|
||||
"bootstrap": "3.3.7",
|
||||
"d3": "4.9.1",
|
||||
"file-loader": "0.11.1",
|
||||
"lodash": "4.17.4",
|
||||
"react": "15.6.x",
|
||||
"react-dom": "15.6.x",
|
||||
"react-dom-factories": "1.0.0",
|
||||
"react-redux": "5.0.x",
|
||||
"redux": "3.6.x",
|
||||
"url-search-params-polyfill": "1.2.0",
|
||||
"ag-grid": "13.0.x",
|
||||
"ag-grid-enterprise": "13.0.x",
|
||||
"ag-grid-react": "13.0.x"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import * as PropTypes from 'prop-types';
|
||||
|
||||
export default class SimpleCellRenderer extends React.Component {
|
||||
render() {
|
||||
@@ -11,5 +12,5 @@ export default class SimpleCellRenderer extends React.Component {
|
||||
}
|
||||
|
||||
SimpleCellRenderer.propTypes = {
|
||||
params: React.PropTypes.object
|
||||
params: PropTypes.object
|
||||
};
|
||||
@@ -1,83 +0,0 @@
|
||||
import SkillsCellRenderer from './SkillsCellRenderer.jsx';
|
||||
import NameCellEditor from './NameCellEditor.jsx';
|
||||
import ProficiencyCellRenderer from './ProficiencyCellRenderer.jsx';
|
||||
import RefData from './RefData';
|
||||
import SkillsFilter from './SkillsFilter.jsx';
|
||||
import ProficiencyFilter from './ProficiencyFilter.jsx';
|
||||
|
||||
export default class ColDefFactory {
|
||||
|
||||
createColDefs() {
|
||||
|
||||
var columnDefs = [
|
||||
{headerName: '#', width: 30, checkboxSelection: true, suppressSorting: true,
|
||||
suppressMenu: true, pinned: true},
|
||||
{
|
||||
headerName: 'Employee',
|
||||
children: [
|
||||
{headerName: "Name", field: "name", enableRowGroup: true, enablePivot: true,
|
||||
width: 150, pinned: true, editable: true,
|
||||
// use a React cellEditor
|
||||
cellEditorFramework: NameCellEditor
|
||||
},
|
||||
{headerName: "Country", field: "country", width: 150, enableRowGroup: true, enablePivot: true,
|
||||
// an example of using a non-React cell renderer
|
||||
cellRenderer: countryCellRenderer, pinned: true,
|
||||
filterParams: {cellRenderer: countryCellRenderer, cellHeight: 20}}
|
||||
,
|
||||
{headerName: "DOB", field: "dob", width: 90, enableRowGroup: true, enablePivot: true, filter:'date', cellRenderer: function(params) {
|
||||
return pad(params.value.getDate(), 2) + '/' +
|
||||
pad(params.value.getMonth() + 1, 2)+ '/' +
|
||||
params.value.getFullYear();
|
||||
}}
|
||||
]
|
||||
},
|
||||
{
|
||||
headerName: 'IT Skills',
|
||||
children: [
|
||||
{headerName: "Skills", width: 125, suppressSorting: true, field: 'skills', enableRowGroup: true, enablePivot: true,
|
||||
// supply a React component
|
||||
cellRendererFramework: SkillsCellRenderer,
|
||||
// supply a React component
|
||||
filterFramework: SkillsFilter
|
||||
},
|
||||
{headerName: "Proficiency", field: "proficiency", width: 120, enableValue: true,
|
||||
// supply a React component
|
||||
cellRendererFramework: ProficiencyCellRenderer,
|
||||
// supply a React component
|
||||
filterFramework: ProficiencyFilter
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
headerName: 'Contact',
|
||||
children: [
|
||||
{headerName: "Mobile", field: "mobile", width: 150, filter: 'text'},
|
||||
{headerName: "Land-line", field: "landline", width: 150, filter: 'text'},
|
||||
{headerName: "Address", field: "address", width: 500, filter: 'text'}
|
||||
]
|
||||
}
|
||||
];
|
||||
return columnDefs;
|
||||
}
|
||||
}
|
||||
|
||||
// this is a simple cell renderer, putting together static html, no
|
||||
// need to use React for it.
|
||||
function countryCellRenderer(params) {
|
||||
if (params.value) {
|
||||
var flag = "<img border='0' width='15' height='10' " +
|
||||
"style='margin-bottom: 2px' src='http://flags.fmcdn.net/data/flags/mini/"
|
||||
+ RefData.COUNTRY_CODES[params.value] + ".png'>";
|
||||
return flag + " " + params.value;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//Utility function used to pad the date formatting.
|
||||
function pad(num, totalStringSize) {
|
||||
let asString = num + "";
|
||||
while (asString.length < totalStringSize) asString = "0" + asString;
|
||||
return asString;
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
});
|
||||
16
src-trader-dashboard/actions/fxDataActions.js
Normal 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)
|
||||
};
|
||||
}
|
||||
|
||||
28
src-trader-dashboard/components/ControlPanel.jsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import React, {Component} from "react";
|
||||
|
||||
export default class extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onExchangeChanged = this.onExchangeChanged.bind(this);
|
||||
}
|
||||
|
||||
onExchangeChanged(event) {
|
||||
this.props.onExchangeChanged(event.target.value);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<span style={{marginRight: 15}}>Control Panel</span>
|
||||
<select value={this.props.selectedExchange.symbol} onChange={this.onExchangeChanged}>
|
||||
{
|
||||
this.props.exchanges.map((exchange) => {
|
||||
return <option key={exchange.symbol} value={exchange.symbol}>{exchange.name}</option>
|
||||
})
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
26
src-trader-dashboard/components/FxPanel.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
};
|
||||
87
src-trader-dashboard/components/FxQuoteMatrix.jsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import React, {Component} from "react";
|
||||
import {connect} from "react-redux";
|
||||
|
||||
import {AgGridReact} from "ag-grid-react";
|
||||
|
||||
import assign from "lodash/assign";
|
||||
import uniq from "lodash/uniq";
|
||||
|
||||
class FxQuoteMatrix extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
columnDefs: this.props.fxDataService.getFxMatrixHeaderNames()
|
||||
};
|
||||
|
||||
// grid events
|
||||
this.onGridReady = this.onGridReady.bind(this);
|
||||
|
||||
// grid callbacks
|
||||
this.getRowNodeId = this.getRowNodeId.bind(this);
|
||||
}
|
||||
|
||||
onGridReady(params) {
|
||||
this.gridApi = params.api;
|
||||
this.columnApi = params.columnApi;
|
||||
|
||||
if (this.props.rowData) {
|
||||
this.gridApi.setRowData(this.props.rowData)
|
||||
}
|
||||
}
|
||||
|
||||
getRowNodeId(data) {
|
||||
return data.symbol;
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const newRowData = nextProps.rowData;
|
||||
|
||||
const updatedRows = [];
|
||||
|
||||
for (let i = 0; i < newRowData.length; i++) {
|
||||
let newRow = newRowData[i];
|
||||
let currentRowNode = this.gridApi.getRowNode(newRow.symbol);
|
||||
|
||||
const {data} = currentRowNode;
|
||||
for (const def of this.state.columnDefs) {
|
||||
if (data[def.field] !== newRow[def.field]) {
|
||||
updatedRows.push(newRow);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.gridApi.updateRowData({update: updatedRows});
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={{height: 410, width: 800}}
|
||||
className="ag-fresh">
|
||||
<AgGridReact
|
||||
// properties
|
||||
columnDefs={this.state.columnDefs}
|
||||
enableSorting="false"
|
||||
enableFilter="false"
|
||||
|
||||
// callbacks
|
||||
getRowNodeId={this.getRowNodeId}
|
||||
|
||||
// events
|
||||
onGridReady={this.onGridReady}>
|
||||
</AgGridReact>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
(state) => {
|
||||
return {
|
||||
rowData: state.fxData
|
||||
}
|
||||
}
|
||||
)(FxQuoteMatrix);
|
||||
171
src-trader-dashboard/components/PriceChangesGrid.jsx
Normal file
@@ -0,0 +1,171 @@
|
||||
import React, {Component} from "react";
|
||||
|
||||
import {AgGridReact} from "ag-grid-react";
|
||||
|
||||
import map from "lodash/map";
|
||||
import difference from "lodash/difference";
|
||||
import forEach from "lodash/forEach";
|
||||
import includes from "lodash/includes";
|
||||
|
||||
import ExchangeService from "../services/ExchangeService.jsx";
|
||||
|
||||
export default class extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
columnDefs: [
|
||||
{
|
||||
field: 'symbol',
|
||||
headerName: 'Symbol',
|
||||
sort: 'asc'
|
||||
},
|
||||
{
|
||||
field: 'price',
|
||||
headerName: 'Price',
|
||||
valueFormatter: this.numberFormatter,
|
||||
cellRenderer: 'animateShowChange',
|
||||
cellStyle: {'text-align': 'right'}
|
||||
},
|
||||
{
|
||||
field: 'bid',
|
||||
headerName: 'Bid',
|
||||
valueFormatter: this.numberFormatter,
|
||||
cellRenderer: 'animateShowChange',
|
||||
cellStyle: {'text-align': 'right'}
|
||||
},
|
||||
{
|
||||
field: 'ask',
|
||||
headerName: 'Ask',
|
||||
valueFormatter: this.numberFormatter,
|
||||
cellRenderer: 'animateShowChange',
|
||||
cellStyle: {'text-align': 'right'}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
this.exchangeService = new ExchangeService();
|
||||
|
||||
// grid events
|
||||
this.onGridReady = this.onGridReady.bind(this);
|
||||
this.onSelectionChanged = this.onSelectionChanged.bind(this);
|
||||
|
||||
// grid callbacks
|
||||
this.getRowNodeId = this.getRowNodeId.bind(this);
|
||||
|
||||
// component events
|
||||
this.updateSymbol = this.updateSymbol.bind(this);
|
||||
}
|
||||
|
||||
numberFormatter(params) {
|
||||
if (typeof params.value === 'number') {
|
||||
return params.value.toFixed(2);
|
||||
} else {
|
||||
return params.value;
|
||||
}
|
||||
}
|
||||
|
||||
onGridReady(params) {
|
||||
this.gridApi = params.api;
|
||||
this.columnApi = params.columnApi;
|
||||
|
||||
// make realistic - call in a batch
|
||||
let rowData = map(this.props.selectedExchange.supportedStocks, symbol => this.exchangeService.getTicker(symbol));
|
||||
this.gridApi.updateRowData({add: rowData});
|
||||
|
||||
// select the first symbol to show the chart
|
||||
this.gridApi.getModel().getRow(0).setSelected(true);
|
||||
|
||||
this.gridApi.sizeColumnsToFit();
|
||||
}
|
||||
|
||||
getRowNodeId(data) {
|
||||
return 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.updateSymbol, symbol);
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.exchangeService.removeSubscribers();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.selectedExchange.supportedStocks !== this.props.selectedExchange.supportedStocks) {
|
||||
if (!this.gridApi) {
|
||||
return;
|
||||
}
|
||||
this.gridApi.deselectAll();
|
||||
|
||||
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.updateSymbol, symbol);
|
||||
});
|
||||
|
||||
// Remove ag-grid nodes as necessary
|
||||
const rowsToRemove = [];
|
||||
this.gridApi.forEachNode(node => {
|
||||
const {data} = node;
|
||||
if (includes(symbolsRemoved, data.symbol)) {
|
||||
rowsToRemove.push(data);
|
||||
}
|
||||
});
|
||||
this.gridApi.updateRowData({remove: rowsToRemove});
|
||||
|
||||
// Subscribe to new ones that need to be added
|
||||
const symbolsAdded = difference(nextSymbols, currentSymbols);
|
||||
forEach(symbolsAdded, symbol => {
|
||||
this.exchangeService.addSubscriber(this.updateSymbol, symbol);
|
||||
});
|
||||
|
||||
// Insert new ag-grid nodes as necessary
|
||||
let rowData = map(symbolsAdded, symbol => this.exchangeService.getTicker(symbol));
|
||||
this.gridApi.updateRowData({add: rowData});
|
||||
|
||||
// select the first symbol to show the chart
|
||||
this.gridApi.getModel().getRow(0).setSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
updateSymbol(symbol) {
|
||||
if (!this.gridApi) {
|
||||
// the grid isn't ready yet
|
||||
return;
|
||||
}
|
||||
|
||||
this.gridApi.updateRowData({update: [symbol]});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={{height: 410, width: 800}}
|
||||
className="ag-fresh">
|
||||
<AgGridReact
|
||||
// properties
|
||||
columnDefs={this.state.columnDefs}
|
||||
enableSorting="true"
|
||||
rowSelection="single"
|
||||
|
||||
// callbacks
|
||||
getRowNodeId={this.getRowNodeId}
|
||||
|
||||
// events
|
||||
onGridReady={this.onGridReady}
|
||||
onSelectionChanged={this.onSelectionChanged}>
|
||||
</AgGridReact>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
57
src-trader-dashboard/components/StockDetailPanel.jsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import React, {Component} from "react";
|
||||
|
||||
import ExchangeService from "../services/ExchangeService.jsx";
|
||||
|
||||
import StockPriceDeltaPanel from "./StockPriceDeltaPanel.jsx";
|
||||
import StockTimestampPanel from "./StockTimestampPanel.jsx";
|
||||
import StockSummaryPanel from "./StockSummaryPanel.jsx";
|
||||
import StockHistoricalChartPanel from "./StockHistoricalChartPanel.jsx";
|
||||
|
||||
export default class extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.exchangeService = new ExchangeService();
|
||||
|
||||
this.state = {
|
||||
priceDelta: null,
|
||||
timestamp: null,
|
||||
tickerSummary: null,
|
||||
historicalData: null
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.selectedSymbol &&
|
||||
nextProps.selectedSymbol !== this.props.selectedSymbol) {
|
||||
let stockDetail = this.exchangeService.getTickerDetail(nextProps.selectedSymbol);
|
||||
|
||||
this.setState({
|
||||
pricingDelta: stockDetail.pricingDelta,
|
||||
timestamp: stockDetail.timestamp,
|
||||
tickerSummary: stockDetail.tickerSummary,
|
||||
historicalData: stockDetail.historicalData
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return nextProps.selectedSymbol !== this.props.selectedSymbol;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.props.selectedSymbol) {
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<StockPriceDeltaPanel pricingDelta={this.state.pricingDelta}/>
|
||||
<StockTimestampPanel timestamp={this.state.timestamp}
|
||||
exchangeName={this.props.exchangeName}/>
|
||||
<StockSummaryPanel tickerSummary={this.state.tickerSummary}/>
|
||||
<StockHistoricalChartPanel historicalData={this.state.historicalData}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
128
src-trader-dashboard/components/StockHistoricalChartPanel.jsx
Normal file
@@ -0,0 +1,128 @@
|
||||
import React, {Component} from "react";
|
||||
|
||||
import * as d3 from "d3";
|
||||
|
||||
export default class extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.margin = {
|
||||
top: 20,
|
||||
right: 20,
|
||||
bottom: 50,
|
||||
left: 0
|
||||
};
|
||||
|
||||
this.renderingWidth = this.props.graphWidth - this.margin.left - this.margin.right;
|
||||
this.renderingheight = this.props.graphHeight - this.margin.top - this.margin.bottom;
|
||||
|
||||
this.x = d3.scaleTime()
|
||||
.range([0, this.renderingWidth]);
|
||||
|
||||
this.y = d3.scaleLinear()
|
||||
.rangeRound([this.renderingheight, 0]);
|
||||
|
||||
this.line = d3.line()
|
||||
.x(d => this.x(d.date))
|
||||
.y(d => this.y(d.price));
|
||||
}
|
||||
|
||||
static get defaultProps() {
|
||||
return {
|
||||
graphHeight: 230,
|
||||
graphWidth: 400
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.renderGraph()
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.renderGraph()
|
||||
}
|
||||
|
||||
renderGraph() {
|
||||
if (!this.props.historicalData) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data = this.props.historicalData;
|
||||
let parseTime = d3.timeParse("%d-%m-%Y");
|
||||
data.forEach((datum) => {
|
||||
datum.date = parseTime(datum.date);
|
||||
datum.price = +datum.price;
|
||||
});
|
||||
|
||||
// clear out any previous graph
|
||||
d3.select(this.refs.historyGraph)
|
||||
.selectAll("svg")
|
||||
.remove();
|
||||
|
||||
// create a new one
|
||||
let svg = d3.select(this.refs.historyGraph)
|
||||
.append('svg')
|
||||
.attr('height', this.props.graphHeight)
|
||||
.attr('width', this.props.graphWidth);
|
||||
|
||||
let g = svg.append("g")
|
||||
.attr("transform", "translate(" + this.margin.left + "," + this.margin.top + ")");
|
||||
|
||||
this.x.domain(d3.extent(data, d => d.date));
|
||||
|
||||
this.y.domain(d3.extent(data, d => d.price));
|
||||
|
||||
let scale = d3.scaleTime()
|
||||
.domain(d3.extent(data, d => d.date))
|
||||
.range([0, this.renderingWidth]);
|
||||
|
||||
// Add the x Axis
|
||||
svg.append("g")
|
||||
.attr("class", "axis")
|
||||
.attr("transform", "translate(0," + (this.renderingheight + 20 ) + ")")
|
||||
.call(d3.axisBottom(scale).tickFormat(d3.timeFormat("%Y-%m-%d")))
|
||||
.selectAll("text")
|
||||
.attr("y", 0)
|
||||
.attr("x", 9)
|
||||
.attr("dy", ".35em")
|
||||
.attr("transform", "rotate(55)")
|
||||
.style("text-anchor", "start");
|
||||
|
||||
// Add the y Axis
|
||||
svg.append("g")
|
||||
.call(d3.axisLeft(this.y));
|
||||
|
||||
g.append("g")
|
||||
.call(d3.axisLeft(this.y))
|
||||
.append("text")
|
||||
.attr("fill", "#000")
|
||||
.attr("transform", "rotate(-90)")
|
||||
.attr("y", 9)
|
||||
.attr("dy", "0.71em")
|
||||
.attr("text-anchor", "end")
|
||||
.text("Price ($)");
|
||||
|
||||
g.append("path")
|
||||
.datum(data)
|
||||
.attr("fill", "none")
|
||||
.attr("stroke", "steelblue")
|
||||
.attr("stroke-linejoin", "round")
|
||||
.attr("stroke-linecap", "round")
|
||||
.attr("stroke-width", 1.5)
|
||||
.attr("d", this.line);
|
||||
}
|
||||
|
||||
render() {
|
||||
let containerStyle = {
|
||||
marginTop: 5
|
||||
};
|
||||
|
||||
if (!this.props.historicalData) {
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<div style={containerStyle} ref="historyGraph"></div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
56
src-trader-dashboard/components/StockPanel.jsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import React, {Component} from "react";
|
||||
|
||||
import PriceChangesGrid from "./PriceChangesGrid.jsx";
|
||||
import StockDetailPanel from "./StockDetailPanel.jsx";
|
||||
import FxPanel from "./FxPanel.jsx";
|
||||
|
||||
export default class extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
selectedSymbol: null
|
||||
};
|
||||
|
||||
this.onSelectionChanged = this.onSelectionChanged.bind(this);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.selectedExchange !== this.props.selectedExchange) {
|
||||
this.setState({
|
||||
selectedSymbol: null
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return nextProps.selectedExchange !== this.props.selectedExchange ||
|
||||
nextState.selectedSymbol !== this.state.selectedSymbol;
|
||||
}
|
||||
|
||||
onSelectionChanged(selectedSymbol) {
|
||||
this.setState({
|
||||
selectedSymbol
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={{width: 1250}}>
|
||||
<div>
|
||||
<div style={{float: "left", marginRight: 25}}>
|
||||
<PriceChangesGrid selectedExchange={this.props.selectedExchange}
|
||||
onSelectionChanged={this.onSelectionChanged}/>
|
||||
</div>
|
||||
<div style={{float: "left"}}>
|
||||
<StockDetailPanel selectedSymbol={this.state.selectedSymbol}
|
||||
exchangeName={this.props.selectedExchange.name}/>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{width: "100%", clear: "both", paddingTop: 25}}>
|
||||
<FxPanel/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
95
src-trader-dashboard/components/StockPriceDeltaPanel.jsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import React, {Component} from "react";
|
||||
|
||||
import isEqual from "lodash/isEqual";
|
||||
|
||||
export default class extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
currentPrice: null,
|
||||
delta: null,
|
||||
deltaPercentage: null
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updatePriceDelta(this.props.pricingDelta);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (!isEqual(this.props.pricingDelta, nextProps.pricingDelta)) {
|
||||
this.updatePriceDelta(nextProps.pricingDelta);
|
||||
}
|
||||
}
|
||||
|
||||
updatePriceDelta(pricingDelta) {
|
||||
let delta = pricingDelta.currentPrice - pricingDelta.previousPrice;
|
||||
let deltaPercentage = (pricingDelta.currentPrice - pricingDelta.previousPrice) / pricingDelta.currentPrice;
|
||||
|
||||
this.setState({
|
||||
currentPrice: pricingDelta.currentPrice,
|
||||
delta,
|
||||
deltaPercentage
|
||||
})
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return !isEqual(this.props.pricingDelta, nextProps.pricingDelta) ||
|
||||
!isEqual(this.state, nextState);
|
||||
}
|
||||
|
||||
numberFormatter(input) {
|
||||
return input ? input.toFixed(2) : null;
|
||||
}
|
||||
|
||||
render() {
|
||||
let containerStyle = {
|
||||
display: "inline-block"
|
||||
};
|
||||
|
||||
let priceStyle = {
|
||||
fontSize: "2.6em",
|
||||
fontWeight: "bold",
|
||||
marginRight: 10
|
||||
};
|
||||
|
||||
let deltaStyle = {
|
||||
fontWeight: "normal",
|
||||
fontSize: "1.8em",
|
||||
verticalAlign: "bottom"
|
||||
};
|
||||
|
||||
let negativeSwingStyle = {
|
||||
color: "#d14836",
|
||||
marginRight: 5
|
||||
};
|
||||
|
||||
let positiveSwingStyle = {
|
||||
color: "#093",
|
||||
marginRight: 5
|
||||
};
|
||||
|
||||
let swingStyle = this.state.delta >= 0 ? positiveSwingStyle : negativeSwingStyle;
|
||||
|
||||
if (!this.props.pricingDelta) {
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<span style={priceStyle}>
|
||||
{this.numberFormatter(this.state.currentPrice)}
|
||||
</span>
|
||||
<div style={containerStyle}>
|
||||
<span style={deltaStyle}>
|
||||
<span style={swingStyle}>{this.numberFormatter(this.state.delta)}</span>
|
||||
<span
|
||||
style={swingStyle}>({this.numberFormatter(this.state.deltaPercentage)}%)</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
78
src-trader-dashboard/components/StockSummaryPanel.jsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import React, {Component} from "react";
|
||||
|
||||
export default class extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
let containerStyle = {
|
||||
fontSize: 13
|
||||
};
|
||||
|
||||
let tableStyle = {
|
||||
display: "inline-block",
|
||||
verticalAlign: "top",
|
||||
borderCollapse: "collapse"
|
||||
|
||||
};
|
||||
|
||||
let keyStyle = {
|
||||
color: "#666"
|
||||
};
|
||||
|
||||
let valueStyle = {
|
||||
textAlign: "right"
|
||||
};
|
||||
|
||||
if (!this.props.tickerSummary) {
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<div style={containerStyle}>
|
||||
<table style={tableStyle}>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style={keyStyle}>Range</td>
|
||||
<td style={valueStyle}>{this.props.tickerSummary.range}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={keyStyle}>52 week</td>
|
||||
<td style={valueStyle}>{this.props.tickerSummary.fiftyTwoWeek}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={keyStyle}>Open</td>
|
||||
<td style={valueStyle}>{this.props.tickerSummary.open}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={keyStyle}>Vol / Avg.</td>
|
||||
<td style={valueStyle}>{this.props.tickerSummary.vol}/{this.props.tickerSummary.avg}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table style={tableStyle}>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style={keyStyle}>Div/yield</td>
|
||||
<td style={valueStyle}>{this.props.tickerSummary.dividend}/{this.props.tickerSummary.yld}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={keyStyle}>EPS</td>
|
||||
<td style={valueStyle}>{this.props.tickerSummary.eps}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={keyStyle}>Shares</td>
|
||||
<td style={valueStyle}>{this.props.tickerSummary.shares}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={keyStyle}>Market Cap</td>
|
||||
<td style={valueStyle}>{this.props.tickerSummary.marketCap}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
30
src-trader-dashboard/components/StockTimestampPanel.jsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React, {Component} from "react";
|
||||
|
||||
export default class extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
let minorStyle = {
|
||||
fontSize: 11,
|
||||
color: "#6F6F6F"
|
||||
};
|
||||
|
||||
if (!this.props.timestamp) {
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
{this.props.timestamp}
|
||||
<div style={minorStyle}>
|
||||
<span>{this.props.exchangeName}</span>
|
||||
<div>Currency in USD</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
90
src-trader-dashboard/components/TopMoversGrid.jsx
Normal file
@@ -0,0 +1,90 @@
|
||||
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);
|
||||
|
||||
// grid callbacks
|
||||
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 (
|
||||
<div style={{height: 410, width: 400}}
|
||||
className="ag-fresh">
|
||||
<AgGridReact
|
||||
// properties
|
||||
columnDefs={this.state.columnDefs}
|
||||
rowData={this.props.rowData}
|
||||
enableSorting
|
||||
enableFilter="false"
|
||||
animateRows
|
||||
deltaRowDataMode
|
||||
getRowNodeId={this.getRowNodeId}
|
||||
|
||||
// events
|
||||
onGridReady={this.onGridReady}>
|
||||
</AgGridReact>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
(state) => {
|
||||
return {
|
||||
rowData: state.fxTopMovers
|
||||
}
|
||||
}
|
||||
)(TopMoversGrid);
|
||||
43
src-trader-dashboard/components/TraderDashboard.jsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import React, {Component} from "react";
|
||||
|
||||
import ExchangeService from "../services/ExchangeService.jsx";
|
||||
|
||||
import ControlPanel from "./ControlPanel.jsx";
|
||||
import StockPanel from "./StockPanel.jsx";
|
||||
|
||||
|
||||
export default class TraderDashboard extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.exchangeService = new ExchangeService();
|
||||
|
||||
this.state = {
|
||||
selectedExchange: this.exchangeService.getExchangeInformation("NASDAQ"),
|
||||
exchanges: this.exchangeService.getExchanges()
|
||||
};
|
||||
|
||||
this.onExchangeChanged = this.onExchangeChanged.bind(this);
|
||||
}
|
||||
|
||||
onExchangeChanged(selectedExchange) {
|
||||
this.setState({
|
||||
selectedExchange: this.exchangeService.getExchangeInformation(selectedExchange)
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div style={{marginTop: 25, marginBottom: 25}}>
|
||||
<ControlPanel
|
||||
exchanges={this.state.exchanges}
|
||||
selectedExchange={this.state.selectedExchange}
|
||||
onExchangeChanged={this.onExchangeChanged}>
|
||||
</ControlPanel>
|
||||
</div>
|
||||
<StockPanel selectedExchange={this.state.selectedExchange}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
import React, {Component} from "react";
|
||||
import * as PropTypes from 'prop-types';
|
||||
|
||||
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 (
|
||||
<div style={{position: 'relative'}}>
|
||||
<div style={{width: "50%"}}>
|
||||
<svg width="100%" preserveAspectRatio="none">
|
||||
<rect x={barX} y="0" width={barWidth} height="20px" rx="4" ry="4" style={barStyle}/>
|
||||
</svg>
|
||||
</div>
|
||||
<div style={text}>{pctNetChange}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalBarComponent.propTypes = {
|
||||
params: PropTypes.object
|
||||
};
|
||||
92
src-trader-dashboard/index.html
Normal file
@@ -0,0 +1,92 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<script type="text/javascript" src="dist/react-trader.js" charset="utf-8"></script>
|
||||
|
||||
<style>
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.ag-fresh .ag-value-change-value-highlight {
|
||||
background-color: #afbcff;
|
||||
}
|
||||
|
||||
.align-right {
|
||||
text-align: right
|
||||
}
|
||||
|
||||
.pct-change-green {
|
||||
background-color: lightgreen;
|
||||
}
|
||||
|
||||
.pct-change-amber {
|
||||
background-color: lightgoldenrodyellow;
|
||||
}
|
||||
|
||||
.pct-change-red {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.fx-null {
|
||||
border-top: 20px solid #3ACFD5;
|
||||
border-right: 20px solid #3a4ed5;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
background-position: 0 0, 0 100%;
|
||||
background-repeat: no-repeat;
|
||||
-webkit-background-size: 100% 20px;
|
||||
-moz-background-size: 100% 20px;
|
||||
background-size: 100% 20px;
|
||||
/*background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiB2aWV3Qm94PSIwIDAgMSAxIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJub25lIj48bGluZWFyR3JhZGllbnQgaWQ9Imxlc3NoYXQtZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiMzYWNmZDUiIHN0b3Atb3BhY2l0eT0iMSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzNhNGVkNSIgc3RvcC1vcGFjaXR5PSIxIi8+PC9saW5lYXJHcmFkaWVudD48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMSIgaGVpZ2h0PSIxIiBmaWxsPSJ1cmwoI2xlc3NoYXQtZ2VuZXJhdGVkKSIgLz48L3N2Zz4=),url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiB2aWV3Qm94PSIwIDAgMSAxIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJub25lIj48bGluZWFyR3JhZGllbnQgaWQ9Imxlc3NoYXQtZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiMzYWNmZDUiIHN0b3Atb3BhY2l0eT0iMSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzNhNGVkNSIgc3RvcC1vcGFjaXR5PSIxIi8+PC9saW5lYXJHcmFkaWVudD48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMSIgaGVpZ2h0PSIxIiBmaWxsPSJ1cmwoI2xlc3NoYXQtZ2VuZXJhdGVkKSIgLz48L3N2Zz4=);*/
|
||||
background-image: -webkit-linear-gradient(top, #afbcff 0%, #c1d5c9 100%), -webkit-linear-gradient(top, #afbcff 0%, #c1d5c9 100%);
|
||||
background-image: -moz-linear-gradient(top, #afbcff 0%, #c1d5c9 100%), -moz-linear-gradient(top, #afbcff 0%, #c1d5c9 100%);
|
||||
background-image: -o-linear-gradient(top, #afbcff 0%, #c1d5c9 100%), -o-linear-gradient(top, #afbcff 0%, #c1d5c9 100%);
|
||||
background-image: linear-gradient(to bottom, #afbcff 0%, #c1d5c9 100%), linear-gradient(to bottom, #afbcff 0%, #c1d5c9 100%);
|
||||
}
|
||||
|
||||
.fx-positive {
|
||||
border-top: 20px solid #3ACFD5;
|
||||
border-right: 20px solid #3a4ed5;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
background-position: 0 0, 0 100%;
|
||||
background-repeat: no-repeat;
|
||||
-webkit-background-size: 100% 20px;
|
||||
-moz-background-size: 100% 20px;
|
||||
background-size: 100% 20px;
|
||||
/*background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiB2aWV3Qm94PSIwIDAgMSAxIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJub25lIj48bGluZWFyR3JhZGllbnQgaWQ9Imxlc3NoYXQtZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiMzYWNmZDUiIHN0b3Atb3BhY2l0eT0iMSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzNhNGVkNSIgc3RvcC1vcGFjaXR5PSIxIi8+PC9saW5lYXJHcmFkaWVudD48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMSIgaGVpZ2h0PSIxIiBmaWxsPSJ1cmwoI2xlc3NoYXQtZ2VuZXJhdGVkKSIgLz48L3N2Zz4=),url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiB2aWV3Qm94PSIwIDAgMSAxIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJub25lIj48bGluZWFyR3JhZGllbnQgaWQ9Imxlc3NoYXQtZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiMzYWNmZDUiIHN0b3Atb3BhY2l0eT0iMSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzNhNGVkNSIgc3RvcC1vcGFjaXR5PSIxIi8+PC9saW5lYXJHcmFkaWVudD48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMSIgaGVpZ2h0PSIxIiBmaWxsPSJ1cmwoI2xlc3NoYXQtZ2VuZXJhdGVkKSIgLz48L3N2Zz4=);*/
|
||||
background-image: -webkit-linear-gradient(top, #00FF00 0%, #c1d5c9 100%), -webkit-linear-gradient(top, #00FF00 0%, #c1d5c9 100%);
|
||||
background-image: -moz-linear-gradient(top, #00FF00 0%, #c1d5c9 100%), -moz-linear-gradient(top, #00FF00 0%, #c1d5c9 100%);
|
||||
background-image: -o-linear-gradient(top, #00FF00 0%, #c1d5c9 100%), -o-linear-gradient(top, #00FF00 0%, #c1d5c9 100%);
|
||||
background-image: linear-gradient(to bottom, #00FF00 0%, #c1d5c9 100%), linear-gradient(to bottom, #00FF00 0%, #c1d5c9 100%);
|
||||
}
|
||||
|
||||
.fx-negative {
|
||||
border-top: 20px solid #3ACFD5;
|
||||
border-right: 20px solid #3a4ed5;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
background-position: 0 0, 0 100%;
|
||||
background-repeat: no-repeat;
|
||||
-webkit-background-size: 100% 20px;
|
||||
-moz-background-size: 100% 20px;
|
||||
background-size: 100% 20px;
|
||||
/*background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiB2aWV3Qm94PSIwIDAgMSAxIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJub25lIj48bGluZWFyR3JhZGllbnQgaWQ9Imxlc3NoYXQtZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiMzYWNmZDUiIHN0b3Atb3BhY2l0eT0iMSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzNhNGVkNSIgc3RvcC1vcGFjaXR5PSIxIi8+PC9saW5lYXJHcmFkaWVudD48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMSIgaGVpZ2h0PSIxIiBmaWxsPSJ1cmwoI2xlc3NoYXQtZ2VuZXJhdGVkKSIgLz48L3N2Zz4=),url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiB2aWV3Qm94PSIwIDAgMSAxIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJub25lIj48bGluZWFyR3JhZGllbnQgaWQ9Imxlc3NoYXQtZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIxMDAlIiB5Mj0iMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiMzYWNmZDUiIHN0b3Atb3BhY2l0eT0iMSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzNhNGVkNSIgc3RvcC1vcGFjaXR5PSIxIi8+PC9saW5lYXJHcmFkaWVudD48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMSIgaGVpZ2h0PSIxIiBmaWxsPSJ1cmwoI2xlc3NoYXQtZ2VuZXJhdGVkKSIgLz48L3N2Zz4=);*/
|
||||
background-image: -webkit-linear-gradient(top, #FF0000 0%, #d5b3af 100%), -webkit-linear-gradient(top, #FF0000 0%, #d5b3af 100%);
|
||||
background-image: -moz-linear-gradient(top, #FF0000 0%, #d5b3af 100%), -moz-linear-gradient(top, #FF0000 0%, #d5b3af 100%);
|
||||
background-image: -o-linear-gradient(top, #FF0000 0%, #d5b3af 100%), -o-linear-gradient(top, #FF0000 0%, #d5b3af 100%);
|
||||
background-image: linear-gradient(to bottom, #FF0000 0%, #d5b3af 100%), linear-gradient(to bottom, #FF0000 0%, #d5b3af 100%);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="traderDashboard"></div>
|
||||
</body>
|
||||
</html>
|
||||
24
src-trader-dashboard/index.js
Normal file
@@ -0,0 +1,24 @@
|
||||
'use strict';
|
||||
|
||||
import React from "react";
|
||||
import {render} from "react-dom";
|
||||
|
||||
import {Provider} from "react-redux";
|
||||
|
||||
import "ag-grid-root/dist/styles/ag-grid.css";
|
||||
import "ag-grid-root/dist/styles/theme-fresh.css";
|
||||
|
||||
import StoreService from './services/StoreService';
|
||||
import TraderDashboard from "./components/TraderDashboard.jsx";
|
||||
|
||||
let store = StoreService.STORE;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<TraderDashboard />
|
||||
</Provider>,
|
||||
document.querySelector('#traderDashboard')
|
||||
);
|
||||
});
|
||||
|
||||
16
src-trader-dashboard/reducers/fxDataReducer.jsx
Normal 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;
|
||||
}
|
||||
};
|
||||
341
src-trader-dashboard/services/ExchangeService.jsx
Normal file
@@ -0,0 +1,341 @@
|
||||
import concat from "lodash/concat";
|
||||
import uniq from "lodash/uniq";
|
||||
import find from "lodash/find";
|
||||
import sampleSize from "lodash/sampleSize";
|
||||
import keys from "lodash/keys";
|
||||
|
||||
export default class {
|
||||
constructor() {
|
||||
this.subscribers = {};
|
||||
this.timestamp = new Date();
|
||||
|
||||
// create the initial state of data
|
||||
this.initialiseTickerData();
|
||||
}
|
||||
|
||||
addSubscriber(subscriber, symbol) {
|
||||
if (!this.subscribers[symbol]) {
|
||||
this.subscribers[symbol] = [];
|
||||
}
|
||||
this.subscribers[symbol].push(subscriber);
|
||||
|
||||
if (!this.updateInterval) {
|
||||
this.updateInterval = setInterval(this.applyDeltasToTickerData.bind(this), 500);
|
||||
}
|
||||
}
|
||||
|
||||
removeSubscriber(subscriber, symbol) {
|
||||
let subscribers = this.subscribers[symbol];
|
||||
subscribers.splice(subscribers.indexOf(subscriber), 1);
|
||||
}
|
||||
|
||||
removeSubscribers() {
|
||||
this.subscribers = {};
|
||||
}
|
||||
|
||||
getTicker(symbol) {
|
||||
return this.tickerData[symbol];
|
||||
}
|
||||
|
||||
getExchanges() {
|
||||
return EXCHANGES;
|
||||
}
|
||||
|
||||
getExchangeInformation(exchangeName) {
|
||||
return find(EXCHANGES, (exchange) => {
|
||||
return exchange.symbol === exchangeName;
|
||||
})
|
||||
}
|
||||
|
||||
getTickerDetail(symbol) {
|
||||
return this.createTickerDetail(symbol);
|
||||
}
|
||||
|
||||
/*
|
||||
* the rest of this class exists primarily to create mock data - it can safely be ignored
|
||||
* as it is secondary to the main ideas being demonstrated by the rest of the application
|
||||
*/
|
||||
initialiseTickerData() {
|
||||
this.tickerData = {};
|
||||
|
||||
const allSymbols = uniq(concat(NASDAQ_SYMBOLS, LSE_SYMBOLS, JSE_SYMBOLS, DE_SYMBOLS));
|
||||
allSymbols.forEach((symbol) => {
|
||||
this.tickerData[symbol] = this.generateTickerRow(symbol);
|
||||
});
|
||||
}
|
||||
|
||||
generateTickerRow(symbol) {
|
||||
let price = this.random(10, 600);
|
||||
return {
|
||||
symbol,
|
||||
price,
|
||||
bid: price - this.random(1, 3),
|
||||
ask: price + this.random(1, 3),
|
||||
recommendation: ['Buy', 'Hold', 'Sell'][Math.floor(this.random(0, 2))]
|
||||
}
|
||||
}
|
||||
|
||||
random(min, max) {
|
||||
return parseFloat((Math.random() * (max - min + 1) + min))
|
||||
}
|
||||
|
||||
applyDeltasToTickerData() {
|
||||
let symbols = keys(this.subscribers);
|
||||
let properties = ['price', 'bid', 'ask'];
|
||||
|
||||
let symbolsToAlter = sampleSize(symbols, symbols.length / 4);
|
||||
let propertyToAlter = sampleSize(properties, 1);
|
||||
|
||||
symbolsToAlter.forEach((symbol) => {
|
||||
this.tickerData[symbol] = {
|
||||
symbol,
|
||||
price: this.tickerData[symbol].price,
|
||||
bid: this.tickerData[symbol].bid,
|
||||
ask: this.tickerData[symbol].ask
|
||||
};
|
||||
|
||||
this.tickerData[symbol][propertyToAlter] = +this.tickerData[symbol][propertyToAlter] + this.random(-2, 2);
|
||||
});
|
||||
|
||||
symbols.forEach((symbol) => {
|
||||
this.subscribers[symbol].forEach((subscriber) => {
|
||||
subscriber(this.tickerData[symbol]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
formatNumber(input) {
|
||||
return input.toFixed(2);
|
||||
}
|
||||
|
||||
formatWithDecimalPlaces(x) {
|
||||
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||
}
|
||||
|
||||
createTickerDetail(symbol) {
|
||||
let ticker = this.getTicker(symbol);
|
||||
let currentPrice = ticker.price;
|
||||
let tenthOfCurrentPrice = currentPrice / 10;
|
||||
let previousPrice = +currentPrice + this.random(-tenthOfCurrentPrice, tenthOfCurrentPrice);
|
||||
|
||||
let twentiethOfCurrentPrice = currentPrice / 20;
|
||||
let yearAgoPrice = this.random(-twentiethOfCurrentPrice, twentiethOfCurrentPrice);
|
||||
|
||||
let range = `${this.formatNumber(previousPrice)} - ${this.formatNumber(currentPrice)}`;
|
||||
let fiftyTwoWeek = `${this.formatNumber(yearAgoPrice)} - ${this.formatNumber(currentPrice)}`;
|
||||
|
||||
let open = this.formatNumber(ticker.bid); // not the same, but will do for demo purposes
|
||||
|
||||
let vol = this.formatWithDecimalPlaces(this.random(5000, 20000).toFixed(2));
|
||||
let avg = `${this.formatNumber(this.random(10, 30))}M`;
|
||||
|
||||
let dividend = this.random(0, 1).toFixed(2);
|
||||
let yld = this.random(1, 2).toFixed(2);
|
||||
|
||||
let eps = this.random(5, 10).toFixed(2);
|
||||
|
||||
let shares = `${this.random(3000, 10000).toFixed(2)}M`;
|
||||
|
||||
let marketCap = `${this.random(100000, 900000).toFixed(2)}M`;
|
||||
|
||||
let historicalData = this.generateHistoricalData(100, this.timestamp, currentPrice);
|
||||
|
||||
return {
|
||||
pricingDelta: {
|
||||
currentPrice,
|
||||
previousPrice
|
||||
},
|
||||
timestamp: this.timestamp.toDateString(),
|
||||
tickerSummary: {
|
||||
range,
|
||||
fiftyTwoWeek,
|
||||
open,
|
||||
vol,
|
||||
avg,
|
||||
dividend,
|
||||
yld,
|
||||
eps,
|
||||
shares,
|
||||
marketCap
|
||||
},
|
||||
historicalData
|
||||
}
|
||||
}
|
||||
|
||||
formatDate(date) {
|
||||
return `${date.getDate()}-${date.getMonth() + 1}-${date.getFullYear()}`;
|
||||
}
|
||||
|
||||
generateHistoricalData(numberOfPoints, endDate, endPrice) {
|
||||
let historicalData = [{
|
||||
"date": this.formatDate(endDate),
|
||||
"price": endPrice
|
||||
}
|
||||
];
|
||||
|
||||
let numberOfTransitions = 15;
|
||||
let pointsPerTransition = numberOfPoints / numberOfTransitions;
|
||||
|
||||
let lastDate = endDate;
|
||||
let lastPrice = endPrice;
|
||||
for (let transition = 0; transition < numberOfTransitions; transition++) {
|
||||
let swing = (Math.random() >= 0.5) ? 1 : -1;
|
||||
|
||||
for (let i = 0; i <= pointsPerTransition; i++) {
|
||||
lastDate.setDate(lastDate.getDate() - 1);
|
||||
lastPrice = lastPrice + (swing * this.random(-1, 10));
|
||||
lastPrice = lastPrice < 0 ? 0 : lastPrice;
|
||||
|
||||
historicalData.splice(0, 0, ({
|
||||
"date": this.formatDate(lastDate),
|
||||
"price": lastPrice
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return historicalData;
|
||||
}
|
||||
}
|
||||
|
||||
const NASDAQ_SYMBOLS = [
|
||||
"SNCL.L",
|
||||
"RNK.L",
|
||||
"SWJ.L",
|
||||
"JDT.L",
|
||||
"UANC.L",
|
||||
"SDP.L",
|
||||
"HSBA.L",
|
||||
"XPL.L",
|
||||
"KLR.L",
|
||||
"SSE.L",
|
||||
"JSI.L",
|
||||
"UBMN.L",
|
||||
"WPC.L",
|
||||
"VTC.L",
|
||||
"UTG.L",
|
||||
"DOR.L",
|
||||
"44RS.L",
|
||||
"GPOR.L",
|
||||
"ASL.L",
|
||||
"40JP.L",
|
||||
"133716",
|
||||
"PJF.L",
|
||||
"MLC.L",
|
||||
"137817",
|
||||
"GHE.L",
|
||||
"PML.L",
|
||||
"SBRY.L",
|
||||
"LEN.L",
|
||||
"STS.L",
|
||||
"138654",
|
||||
"PTEC.L"
|
||||
];
|
||||
|
||||
const LSE_SYMBOLS = [
|
||||
"PVG.L",
|
||||
"SN.L,",
|
||||
"SWJ.L",
|
||||
"JDT.L",
|
||||
"UANC.L",
|
||||
"SDP.L",
|
||||
"HSBA.L",
|
||||
"XPL.L",
|
||||
"KLR.L",
|
||||
"SSE.L",
|
||||
"JSI.L",
|
||||
"UBMN.L",
|
||||
"DLN.L",
|
||||
"SIR.L",
|
||||
"SEC.L",
|
||||
"DOR.L",
|
||||
"44RS.L",
|
||||
"GPOR.L",
|
||||
"ASL.L",
|
||||
"40JP.L",
|
||||
"133716",
|
||||
"PJF.L",
|
||||
"MLC.L",
|
||||
"137817",
|
||||
"GHE.L",
|
||||
"PML.L",
|
||||
"SBRY.L",
|
||||
"LEN.L",
|
||||
"MAV4.L",
|
||||
"GLEN.L",
|
||||
"EDGD.L",
|
||||
];
|
||||
|
||||
const JSE_SYMBOLS = [
|
||||
"ECV.L",
|
||||
"MHN.L",
|
||||
"SWJ.L",
|
||||
"JDT.L",
|
||||
"UANC.L",
|
||||
"PLAZ.L",
|
||||
"CLDN.L",
|
||||
"XPL.L",
|
||||
"KLR.L",
|
||||
"SSE.L",
|
||||
"JSI.L",
|
||||
"UBMN.L",
|
||||
"WPC.L",
|
||||
"VTC.L",
|
||||
"UTG.L",
|
||||
"DOR.L",
|
||||
"44RS.L",
|
||||
"GPOR.L",
|
||||
"ASL.L",
|
||||
"40JP.L",
|
||||
"133716",
|
||||
"CRW.L",
|
||||
"JPR.L",
|
||||
"UTLC.L",
|
||||
"GHS.L",
|
||||
"PML.L",
|
||||
"SBRY.L",
|
||||
"LEN.L",
|
||||
"STS.L",
|
||||
"138654",
|
||||
"RWS.L"
|
||||
];
|
||||
|
||||
const DE_SYMBOLS = [
|
||||
"ECV.L",
|
||||
"MHN.L",
|
||||
"SWJ.L",
|
||||
"JDT.L",
|
||||
"UANC.L",
|
||||
"SDP.L",
|
||||
"KBC.L",
|
||||
"VM.L,",
|
||||
"KLR.L",
|
||||
"SSE.L",
|
||||
"JSI.L",
|
||||
"UBMN.L",
|
||||
"WPC.L",
|
||||
"VTC.L",
|
||||
"UTG.L",
|
||||
"DOR.L",
|
||||
"44RS.L",
|
||||
"GPOR.L",
|
||||
"ASL.L",
|
||||
"40JP.L",
|
||||
"133716",
|
||||
"PJF.L",
|
||||
"MLC.L",
|
||||
"DPV6.L",
|
||||
"LMIN.L",
|
||||
"PML.L",
|
||||
"SBRY.L",
|
||||
"LEN.L",
|
||||
"STS.L",
|
||||
"BKIR.L",
|
||||
"AFMF.L",
|
||||
];
|
||||
|
||||
const EXCHANGES = [
|
||||
{name: 'Nasdaq Stock Market', symbol: 'NASDAQ', supportedStocks: NASDAQ_SYMBOLS},
|
||||
{name: 'London Stock Exchange', symbol: 'LSE', supportedStocks: LSE_SYMBOLS},
|
||||
{name: 'Japan Exchange Group', symbol: 'JSE', supportedStocks: JSE_SYMBOLS},
|
||||
{name: 'Deutsche Börse', symbol: 'DE', supportedStocks: DE_SYMBOLS}
|
||||
];
|
||||
201
src-trader-dashboard/services/FxDataService.jsx
Normal file
@@ -0,0 +1,201 @@
|
||||
import sampleSize from "lodash/sampleSize";
|
||||
import cloneDeep from "lodash/cloneDeep";
|
||||
|
||||
import StoreService from "./StoreService";
|
||||
import {fxDataUpdated, fxTopMoversUpdated} from "../actions/fxDataActions";
|
||||
|
||||
import HorizontalBarComponent from "../components/renderers/HorizontalBarComponent.jsx";
|
||||
|
||||
export default class {
|
||||
constructor() {
|
||||
this.store = StoreService.STORE;
|
||||
|
||||
// create the initial state of the fx data
|
||||
this.createFxMatrixSnapshot();
|
||||
this.calculateTopMovers();
|
||||
|
||||
// and dispatch it
|
||||
this.store.dispatch(fxDataUpdated(this.fxData));
|
||||
this.store.dispatch(fxTopMoversUpdated(this.fxTopMovers));
|
||||
|
||||
// periodically update the fx and top mover information
|
||||
this.kickOffPeriodicUpdates();
|
||||
}
|
||||
|
||||
kickOffPeriodicUpdates() {
|
||||
setInterval(() => {
|
||||
this.applyDeltasToFxData();
|
||||
|
||||
this.store.dispatch(fxDataUpdated(this.fxData));
|
||||
}, 1500);
|
||||
|
||||
setInterval(() => {
|
||||
this.calculateTopMovers();
|
||||
|
||||
this.store.dispatch(fxTopMoversUpdated(this.fxTopMovers));
|
||||
}, 2500);
|
||||
}
|
||||
|
||||
/*
|
||||
* the rest of this class exists primarily to create mock data - it can safely be ignored
|
||||
* as it is secondary to the main ideas being demonstrated by the rest of the application
|
||||
*/
|
||||
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() {
|
||||
let fxSymbolsToUpdate = sampleSize(FX_CURRENCY_SYMBOLS, FX_CURRENCY_SYMBOLS.length / 5);
|
||||
let rowsToUpdate = sampleSize(this.fxData, this.fxData.length / 5);
|
||||
for (let i = 0; i < rowsToUpdate.length; i++) {
|
||||
let row = rowsToUpdate[i];
|
||||
|
||||
let maxSwing = row.last * 0.05; // max 5% swing
|
||||
let swing = Math.floor(this.random(-maxSwing, maxSwing));
|
||||
row.last = row.last + swing;
|
||||
row.net = swing;
|
||||
|
||||
let multiplier = ((Math.random() * 10) > 5) ? -1 : 1;
|
||||
row['pct_net_change'] = multiplier * (this.random(30, 100) / 100).toFixed(2);
|
||||
|
||||
for (let symbolToUpdate of fxSymbolsToUpdate) {
|
||||
if (row[symbolToUpdate] === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let multiplier = ((Math.random() * 10) > 8) ? -1 : 1;
|
||||
row[symbolToUpdate] = (multiplier * Math.random()).toFixed(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
random(min, max) {
|
||||
return parseFloat((Math.random() * (max - min + 1) + min))
|
||||
}
|
||||
|
||||
getFxMatrixHeaderNames() {
|
||||
return FX_DELTA_HEADERS;
|
||||
}
|
||||
|
||||
createFxMatrixSnapshot() {
|
||||
let columns = FX_CURRENCY_MATRIX[0];
|
||||
let data = FX_CURRENCY_MATRIX.slice(1);
|
||||
|
||||
let rowData = [];
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
let currentRow = data[i];
|
||||
|
||||
let row = {};
|
||||
for (let j = 0; j < columns.length; j++) {
|
||||
row[columns[j]] = currentRow[j];
|
||||
}
|
||||
|
||||
// last, net and % change are different
|
||||
row['last'] = Math.floor(this.random(7000, 170000));
|
||||
row['net'] = Math.floor(this.random(-500, 500));
|
||||
|
||||
let multiplier = ((Math.random() * 10) > 5) ? -1 : 1;
|
||||
row['pct_net_change'] = multiplier * (this.random(30, 100) / 100).toFixed(2);
|
||||
|
||||
rowData.push(row);
|
||||
}
|
||||
|
||||
this.fxData = rowData;
|
||||
}
|
||||
}
|
||||
|
||||
const FX_CURRENCY_SYMBOLS = ["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 FX_DELTA_HEADERS = [
|
||||
{
|
||||
field: 'symbol',
|
||||
headerName: 'Symbol',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
field: 'last',
|
||||
headerName: 'Last',
|
||||
headerClass: 'align-right',
|
||||
cellRenderer: 'animateShowChange',
|
||||
cellClass: 'align-right',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
field: 'net',
|
||||
headerName: 'Net',
|
||||
headerClass: 'align-right',
|
||||
cellRenderer: 'animateShowChange',
|
||||
cellClass: 'align-right',
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
field: 'pct_net_change',
|
||||
headerName: '% NC',
|
||||
cellRendererFramework: HorizontalBarComponent,
|
||||
width: 85
|
||||
},
|
||||
].concat(FX_CURRENCY_SYMBOLS.map((symbol) => {
|
||||
"use strict";
|
||||
return {
|
||||
field: symbol,
|
||||
headerName: symbol,
|
||||
width: 67,
|
||||
cellClass: 'align-right',
|
||||
cellRenderer: 'animateShowChange',
|
||||
cellClassRules: {
|
||||
'fx-positive': 'x > 0.8',
|
||||
'fx-null': 'x === null',
|
||||
'fx-negative': 'x < -0.8'
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
const FX_CURRENCY_MATRIX = [
|
||||
["symbol", ...FX_CURRENCY_SYMBOLS],
|
||||
["USDGBP", null, "0.89", "-0.01", "0.29", "0.28", "-0.04", "0.47", "0.17", "0.20", "0.11", "0.97", "0.07", "-0.20", "0.57", "0.54", "0.26", "0.33", "0.86", "0.95", "0.39", "0.21", "-0.49", "0.63", "-0.13", "-0.86", "0.18", "-0.84", "-0.89", "0.05", "-0.29", "0.34", "0.93", "0.41", "0.13", "-0.17", "0.70", "-0.17", "0.60", "0.00", "0.09", "-0.15", "0.10"],
|
||||
["USDEUR", "0.89", null, "0.25", "0.29", "0.86", "-0.93", "0.09", "0.63", "0.76", "0.10", "0.51", "-0.31", "-0.51", "0.20", "0.48", "-0.42", "0.73", "0.57", "0.65", "0.40", "0.12", "0.24", "0.42", "0.75", "0.13", "0.75", "0.42", "-0.80", "-0.26", "0.68", "0.68", "0.98", "0.87", "0.48", "0.52", "0.23", "0.61", "0.71", "-0.61", "0.40", "-0.80", "-0.89"],
|
||||
["USDAED", "0.90", "0.95", null, "0.71", "0.49", "0.50", "0.51", "0.07", "0.80", "-0.74", "0.54", "0.46", "-0.90", "-0.33", "0.75", "0.91", "0.84", "0.86", "0.89", "0.68", "0.71", "0.74", "0.58", "-0.24", "0.31", "0.85", "0.28", "0.44", "0.51", "-0.19", "0.71", "0.81", "0.85", "0.23", "0.09", "0.65", "-0.48", "0.43", "0.97", "0.11", "-0.81", "-0.69"],
|
||||
["USDJPY", "0.84", "0.92", "0.36", null, "0.79", "0.90", "0.86", "0.42", "0.14", "0.98", "0.47", "0.76", "0.60", "0.15", "0.04", "0.58", "0.13", "-0.16", "0.63", "0.86", "-0.63", "-0.95", "0.42", "0.08", "0.90", "-0.39", "-0.16", "0.66", "0.95", "0.31", "0.66", "-0.84", "0.06", "-0.60", "0.08", "0.57", "0.93", "0.78", "0.15", "0.36", "0.78", "0.34"],
|
||||
["USDCAD", "0.79", "-0.87", "0.93", "0.83", null, "0.10", "0.60", "0.34", "0.98", "0.70", "0.09", "0.46", "0.80", "0.10", "0.26", "-0.90", "0.69", "-0.22", "0.51", "-0.75", "-0.08", "0.07", "0.24", "0.68", "0.60", "0.37", "0.92", "0.02", "0.66", "0.16", "0.24", "-0.79", "0.19", "0.19", "0.08", "-0.97", "-0.93", "0.04", "-0.92", "0.86", "-0.36", "0.04"],
|
||||
["USDCHF", "0.94", "-0.02", "0.70", "0.82", "0.41", null, "0.76", "0.06", "0.46", "0.99", "0.07", "0.72", "0.14", "0.90", "0.23", "-0.54", "0.64", "0.80", "0.16", "0.23", "0.66", "-0.89", "-0.54", "-0.95", "0.27", "0.56", "0.42", "0.41", "0.88", "0.81", "0.37", "0.75", "0.06", "-0.75", "0.98", "0.21", "0.01", "0.03", "0.35", "-0.42", "0.55", "0.19"],
|
||||
["GBPUSD", "0.05", "-0.64", "0.47", "0.56", "0.51", "-0.47", null, "-0.15", "0.13", "0.55", "0.58", "0.57", "-0.49", "0.28", "0.86", "0.49", "0.12", "-0.91", "0.06", "0.12", "0.18", "-0.02", "-0.55", "0.18", "0.29", "0.76", "0.11", "0.04", "0.55", "0.16", "0.84", "0.83", "-0.94", "0.32", "0.07", "-0.33", "-0.29", "0.01", "-0.19", "0.01", "-0.60", "0.66"],
|
||||
["GBPEUR", "-0.32", "0.17", "0.60", "-0.52", "-0.52", "0.39", "-0.42", null, "0.07", "0.69", "0.67", "0.01", "0.72", "0.35", "0.66", "-0.66", "0.21", "0.12", "0.42", "-0.57", "-0.04", "0.48", "0.91", "0.72", "-0.16", "0.79", "0.03", "0.44", "0.17", "0.87", "-0.82", "0.20", "0.88", "0.48", "0.59", "-0.70", "0.12", "0.56", "-0.69", "0.10", "0.90", "1.00"],
|
||||
["GBPAED", "0.76", "0.05", "0.72", "-0.40", "0.93", "0.64", "0.90", "0.21", null, "0.91", "0.33", "-0.36", "0.28", "0.87", "0.25", "0.33", "0.44", "0.89", "0.17", "0.96", "0.86", "0.88", "0.03", "-0.25", "0.21", "0.98", "-0.60", "0.07", "0.90", "0.23", "0.41", "0.77", "0.83", "0.94", "0.12", "0.37", "0.58", "0.35", "-0.53", "0.49", "0.96", "-0.06"],
|
||||
["GBPJPY", "0.63", "0.72", "0.59", "0.89", "-0.54", "0.97", "0.61", "0.64", "0.85", null, "0.35", "0.57", "0.25", "0.85", "0.27", "-0.70", "-0.30", "-0.36", "0.29", "0.82", "0.26", "0.69", "0.46", "0.01", "0.09", "0.78", "0.95", "0.16", "0.07", "0.41", "0.99", "0.67", "0.34", "-0.89", "0.83", "0.32", "0.21", "-0.48", "-0.64", "0.65", "0.58", "0.69"],
|
||||
["GBPCAD", "0.78", "0.09", "0.33", "0.11", "0.95", "0.01", "0.96", "0.72", "0.13", "0.02", null, "0.44", "0.63", "-0.52", "0.08", "-0.85", "0.74", "0.41", "0.52", "0.15", "-0.97", "0.61", "0.58", "0.47", "0.56", "0.37", "0.54", "-0.65", "0.98", "0.44", "0.28", "0.70", "0.44", "0.34", "0.95", "0.82", "-0.09", "0.62", "0.87", "0.25", "-1.00", "0.99"],
|
||||
["GBPCHF", "-0.87", "0.68", "0.84", "0.83", "0.50", "0.74", "0.02", "0.11", "-0.12", "0.36", "0.71", null, "0.45", "0.02", "0.51", "0.50", "-0.03", "-0.94", "0.23", "0.38", "0.09", "0.26", "0.14", "0.39", "0.36", "0.01", "0.76", "0.21", "0.03", "0.97", "-0.89", "0.68", "0.43", "0.43", "0.26", "0.01", "0.60", "0.15", "0.77", "0.51", "-0.31", "0.37"],
|
||||
["EURUSD", "-0.45", "-0.05", "0.14", "-0.11", "0.36", "0.12", "0.76", "0.66", "0.50", "0.31", "0.28", "0.90", null, "-0.04", "0.73", "0.33", "0.27", "0.22", "0.86", "0.79", "-0.29", "0.30", "0.89", "0.73", "0.94", "0.24", "0.19", "0.81", "0.88", "0.46", "0.19", "0.79", "-0.16", "0.85", "0.84", "-0.57", "-0.87", "0.86", "0.69", "0.12", "0.64", "0.13"],
|
||||
["EURGBP", "0.06", "0.81", "0.54", "0.57", "0.57", "0.81", "0.38", "0.70", "0.37", "0.58", "-0.56", "0.77", "0.59", null, "0.98", "-0.48", "0.60", "0.78", "0.22", "0.20", "-0.62", "-0.97", "-0.55", "0.39", "0.88", "0.96", "0.32", "0.84", "0.26", "-0.47", "0.04", "0.34", "-0.24", "0.53", "-0.75", "-0.97", "0.47", "0.81", "0.49", "-0.84", "0.94", "0.09"],
|
||||
["EURAED", "0.61", "0.59", "0.11", "0.21", "0.41", "-0.45", "0.32", "0.90", "-0.98", "0.38", "0.27", "0.62", "0.67", "0.89", null, "0.27", "0.65", "0.72", "0.69", "0.66", "0.12", "0.32", "0.11", "-0.73", "-0.17", "0.74", "0.03", "0.96", "0.59", "-0.92", "-0.91", "-0.41", "0.09", "0.45", "0.90", "0.88", "0.96", "0.47", "0.95", "0.27", "-0.48", "0.71"],
|
||||
["EURJPY", "-0.01", "0.36", "0.28", "0.68", "0.50", "0.25", "0.81", "0.64", "0.12", "-0.73", "-0.47", "0.96", "-0.98", "-0.41", "0.78", null, "0.19", "-0.37", "0.24", "0.63", "0.74", "-0.24", "0.46", "0.63", "0.99", "0.65", "0.52", "0.11", "0.52", "0.39", "0.41", "0.51", "0.78", "1.00", "0.80", "-0.11", "0.90", "0.96", "0.66", "0.32", "0.02", "0.19"],
|
||||
["EURCAD", "-0.80", "0.40", "0.35", "0.45", "0.66", "0.21", "0.77", "0.58", "0.09", "1.00", "0.02", "-0.85", "0.89", "0.63", "-0.05", "0.46", null, "0.87", "0.96", "-0.48", "0.40", "0.18", "0.37", "0.81", "0.50", "-0.06", "-0.56", "0.38", "0.02", "0.83", "-0.93", "0.80", "0.31", "0.10", "0.70", "0.55", "0.47", "0.77", "0.98", "0.88", "0.19", "0.91"],
|
||||
["EURCHF", "-0.93", "0.01", "-0.29", "0.46", "-0.59", "-0.15", "0.58", "0.00", "-0.62", "0.36", "0.27", "-0.10", "-0.10", "0.57", "0.57", "0.75", "-0.10", null, "0.88", "0.21", "0.89", "0.85", "0.16", "0.93", "0.60", "0.59", "0.42", "0.70", "0.66", "0.80", "0.83", "0.52", "0.01", "0.41", "0.68", "0.94", "0.81", "0.40", "0.69", "0.04", "0.70", "0.61"],
|
||||
["AEDUSD", "0.10", "0.45", "0.06", "-0.37", "-0.14", "0.63", "0.10", "0.77", "0.95", "-0.12", "1.00", "-0.19", "-0.59", "0.78", "0.10", "0.76", "-0.45", "-0.99", null, "-0.46", "0.28", "0.31", "0.32", "0.42", "-0.00", "0.66", "0.24", "0.31", "0.10", "0.73", "0.02", "0.78", "0.07", "1.00", "0.43", "0.61", "0.53", "0.53", "0.41", "0.37", "0.97", "-0.19"],
|
||||
["AEDGBP", "0.70", "0.73", "0.34", "0.97", "0.84", "0.05", "0.21", "0.14", "0.45", "0.69", "0.41", "-0.53", "0.28", "0.13", "0.99", "0.97", "0.93", "0.62", "0.35", null, "0.18", "0.96", "0.58", "0.04", "0.00", "0.58", "-0.54", "0.35", "0.84", "0.83", "-0.20", "0.51", "0.32", "0.01", "0.95", "0.69", "-0.73", "0.36", "0.78", "0.02", "0.22", "0.39"],
|
||||
["AEDEUR", "0.18", "0.84", "-0.89", "0.90", "-0.25", "0.53", "0.71", "0.80", "0.60", "-0.98", "-0.81", "0.57", "0.72", "0.32", "-0.23", "0.98", "0.50", "0.93", "0.56", "0.26", null, "0.61", "0.47", "0.72", "0.58", "0.19", "0.94", "0.21", "0.47", "0.70", "0.67", "0.85", "0.98", "0.15", "0.21", "0.43", "0.03", "0.92", "0.91", "0.35", "0.70", "0.85"],
|
||||
["AEDJPY", "0.88", "0.62", "0.50", "0.27", "0.56", "0.61", "0.06", "-0.02", "0.42", "0.26", "0.23", "0.83", "-0.55", "0.94", "0.01", "0.72", "-0.43", "0.10", "0.01", "-0.20", "0.13", null, "0.70", "0.10", "0.05", "0.43", "-0.14", "-0.64", "0.80", "0.79", "0.57", "0.14", "0.14", "0.04", "-0.96", "0.59", "0.14", "0.51", "0.23", "0.57", "0.94", "0.88"],
|
||||
["AEDCAD", "0.75", "-0.99", "0.82", "-0.87", "0.79", "1.00", "0.21", "0.30", "-0.40", "0.34", "-0.85", "0.41", "0.79", "0.17", "0.58", "-0.24", "0.18", "0.15", "0.18", "0.44", "0.34", "0.90", null, "0.10", "0.34", "0.31", "0.34", "0.79", "-0.05", "0.19", "-0.86", "0.61", "-0.65", "0.23", "0.59", "0.69", "-0.65", "0.89", "0.97", "0.12", "0.63", "-0.24"],
|
||||
["AEDCHF", "0.67", "0.46", "0.92", "0.22", "0.91", "0.67", "0.81", "0.04", "0.29", "0.13", "0.80", "0.95", "0.29", "0.99", "-0.17", "0.58", "0.64", "0.68", "0.41", "0.69", "0.84", "0.01", "0.04", null, "0.07", "0.16", "0.26", "-0.43", "0.08", "0.58", "0.33", "-0.28", "0.03", "-0.08", "0.54", "-0.91", "0.78", "-0.10", "-0.13", "0.15", "0.62", "-0.13"],
|
||||
["JPYUSD", "-0.67", "0.90", "0.84", "0.10", "0.77", "0.43", "0.59", "0.69", "0.63", "0.82", "-0.61", "0.41", "0.07", "0.48", "1.00", "-0.52", "0.22", "0.89", "0.43", "0.54", "0.56", "-0.43", "0.78", "0.67", null, "0.06", "0.28", "0.04", "0.62", "0.18", "0.77", "0.63", "0.05", "0.87", "0.22", "0.21", "0.07", "0.25", "-0.55", "0.67", "0.10", "-0.67"],
|
||||
["JPYGBP", "0.21", "0.46", "0.23", "0.53", "-0.97", "0.78", "0.43", "0.74", "-0.95", "0.98", "0.49", "0.66", "0.18", "0.55", "-0.33", "0.67", "0.65", "0.23", "-0.07", "-0.67", "0.56", "0.74", "-0.38", "-0.83", "-0.51", null, "-0.26", "-0.79", "0.73", "-0.78", "-0.09", "0.19", "0.48", "0.97", "0.11", "0.19", "0.51", "0.32", "0.81", "-0.02", "0.17", "0.48"],
|
||||
["JPYEUR", "0.83", "0.86", "0.62", "-0.92", "0.64", "0.58", "0.55", "0.07", "0.81", "0.32", "0.33", "0.96", "0.48", "0.57", "0.25", "0.56", "0.33", "0.02", "-0.79", "0.44", "0.95", "0.35", "0.41", "0.06", "0.28", "0.77", null, "0.64", "0.79", "0.74", "0.67", "-0.97", "0.47", "-0.11", "0.03", "0.39", "-0.37", "0.06", "0.09", "0.13", "0.20", "0.87"],
|
||||
["JPYAED", "0.69", "0.80", "0.90", "0.06", "0.55", "0.90", "-0.09", "0.05", "-0.10", "0.31", "0.57", "0.69", "-0.78", "0.42", "0.64", "0.96", "0.08", "-0.76", "0.59", "0.00", "0.40", "-0.88", "0.69", "0.17", "-0.16", "0.57", "0.86", null, "-0.61", "-0.14", "0.41", "-0.16", "0.36", "0.95", "0.44", "0.41", "0.35", "-0.56", "0.25", "0.66", "0.90", "0.82"],
|
||||
["JPYCAD", "0.33", "0.27", "0.61", "0.01", "-0.16", "0.21", "0.57", "-0.41", "-0.56", "0.55", "0.13", "-0.87", "0.19", "0.64", "-0.38", "-0.80", "-0.10", "0.44", "0.72", "-0.72", "0.48", "0.42", "0.84", "0.73", "0.35", "0.68", "0.01", "0.09", null, "0.87", "0.17", "0.45", "0.02", "0.01", "0.77", "0.24", "0.62", "0.39", "-0.73", "0.21", "-0.08", "0.42"],
|
||||
["JPYCHF", "-0.45", "0.52", "0.34", "0.85", "0.16", "0.96", "0.53", "0.62", "0.46", "0.94", "0.08", "-0.68", "0.81", "0.44", "0.27", "0.75", "-0.84", "0.69", "-0.99", "0.41", "0.66", "-0.94", "0.21", "0.85", "-0.08", "0.36", "0.62", "0.25", "-0.48", null, "0.51", "-0.39", "0.60", "0.63", "0.68", "0.10", "0.62", "0.68", "0.00", "0.24", "0.61", "0.23"],
|
||||
["CADUSD", "-0.65", "0.73", "0.93", "0.27", "-0.00", "-0.46", "0.53", "0.36", "0.83", "0.58", "0.40", "0.03", "0.32", "0.38", "-0.53", "0.86", "0.42", "0.90", "0.83", "0.94", "0.34", "-0.56", "0.46", "-0.72", "0.13", "0.08", "0.25", "0.59", "0.75", "0.72", null, "0.62", "0.73", "0.52", "0.28", "0.03", "-0.54", "0.50", "0.76", "-0.28", "0.89", "0.97"],
|
||||
["CADGBP", "0.74", "0.58", "0.55", "0.01", "0.33", "0.61", "0.75", "0.86", "-0.87", "0.50", "0.57", "0.84", "-0.34", "1.00", "0.77", "0.75", "0.40", "0.12", "-0.57", "-0.16", "-0.91", "0.06", "0.67", "-0.55", "0.02", "0.78", "0.57", "0.70", "0.21", "0.95", "-0.91", null, "-0.82", "0.71", "0.36", "0.46", "0.46", "0.24", "0.15", "0.54", "0.15", "0.83"],
|
||||
["CADEUR", "-0.58", "0.29", "0.03", "0.70", "0.77", "0.80", "0.52", "0.04", "0.11", "0.86", "-0.98", "0.02", "0.01", "-0.19", "-0.48", "0.80", "0.90", "0.10", "0.28", "0.88", "0.98", "0.43", "0.47", "-0.41", "-0.92", "0.49", "0.64", "-0.60", "0.11", "0.61", "0.19", "-0.39", null, "-0.21", "0.56", "-0.45", "-0.48", "0.51", "0.04", "-0.44", "-0.22", "-0.29"],
|
||||
["CADAED", "0.07", "0.55", "0.99", "-0.41", "0.65", "0.75", "0.97", "0.74", "0.65", "0.37", "-0.04", "0.85", "0.83", "0.25", "0.14", "-0.00", "0.80", "0.78", "-0.42", "0.54", "-0.42", "-0.07", "-0.62", "0.56", "0.89", "0.92", "0.19", "0.64", "0.45", "0.24", "0.97", "0.41", "0.36", null, "-0.54", "-0.85", "-0.68", "0.48", "0.13", "0.61", "0.75", "0.21"],
|
||||
["CADJPY", "-0.84", "0.71", "0.59", "-0.78", "0.92", "0.93", "0.03", "0.27", "0.22", "0.71", "-0.79", "0.72", "0.27", "0.29", "0.40", "0.66", "0.44", "-0.33", "0.24", "0.99", "-0.95", "0.74", "0.42", "0.45", "0.08", "0.46", "0.22", "0.70", "0.80", "0.36", "0.36", "0.94", "0.75", "0.51", null, "0.10", "0.31", "0.54", "1.00", "0.78", "0.17", "0.32"],
|
||||
["CADCHF", "0.19", "0.00", "0.62", "-0.63", "0.75", "0.57", "0.28", "-0.98", "0.17", "-0.63", "0.09", "0.10", "-0.09", "0.22", "0.65", "0.20", "0.27", "-0.66", "-0.70", "0.72", "0.40", "0.96", "0.52", "0.38", "0.96", "0.90", "0.35", "-0.70", "0.37", "0.94", "0.20", "0.78", "0.34", "0.24", "-0.18", null, "0.05", "0.71", "0.05", "0.38", "0.02", "0.60"],
|
||||
["CHFUSD", "0.42", "0.61", "-0.30", "-0.27", "0.46", "0.63", "0.64", "0.33", "0.18", "0.88", "0.99", "0.73", "0.85", "0.07", "0.53", "0.31", "-0.63", "0.56", "-0.04", "-0.68", "0.43", "-0.96", "0.45", "0.45", "0.50", "0.17", "0.82", "0.34", "0.26", "0.16", "0.71", "0.35", "0.57", "0.79", "0.56", "0.55", null, "0.02", "-0.90", "0.84", "0.33", "0.18"],
|
||||
["CHFGBP", "-0.38", "0.73", "0.25", "0.64", "0.41", "-0.48", "0.75", "-0.40", "0.37", "0.62", "0.30", "0.06", "0.54", "0.15", "0.51", "0.33", "-0.79", "-0.05", "-0.54", "0.21", "0.40", "0.86", "0.96", "0.94", "0.52", "0.07", "0.71", "-0.80", "-0.40", "0.71", "0.71", "0.89", "0.43", "-0.68", "0.93", "0.05", "0.42", null, "0.59", "-0.67", "0.03", "0.17"],
|
||||
["CHFEUR", "-0.64", "0.96", "-0.34", "0.71", "0.53", "0.44", "0.27", "0.56", "0.96", "0.40", "0.86", "0.65", "0.82", "0.89", "-0.75", "0.29", "0.06", "0.70", "0.23", "0.68", "0.79", "0.21", "0.50", "0.14", "-0.27", "-0.83", "-0.22", "0.59", "0.40", "0.10", "0.88", "0.99", "0.21", "-0.36", "0.00", "0.53", "0.08", "0.08", null, "0.53", "-0.51", "0.03"],
|
||||
["CHFAED", "-0.99", "0.71", "0.51", "0.58", "0.57", "0.73", "0.60", "0.23", "0.02", "0.84", "0.90", "0.56", "0.03", "-0.93", "0.59", "0.24", "-0.67", "0.13", "0.99", "0.28", "0.72", "0.79", "-0.83", "-0.30", "0.73", "0.08", "-0.02", "0.85", "0.33", "0.57", "0.09", "0.26", "0.16", "0.80", "0.65", "-0.19", "-0.38", "-0.61", "-0.98", null, "0.57", "-0.81"],
|
||||
["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]
|
||||
];
|
||||
8
src-trader-dashboard/services/StoreService.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import {createStore} from "redux";
|
||||
|
||||
import fxData from "../reducers/fxDataReducer.jsx";
|
||||
|
||||
class StoreService {
|
||||
}
|
||||
StoreService.STORE = createStore(fxData);
|
||||
export default StoreService;
|
||||
103
src/App.jsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import React, {Component} from "react";
|
||||
|
||||
import "url-search-params-polyfill";
|
||||
|
||||
import DynamicComponentsExample from "./dynamicComponentExample/DynamicComponentsExample";
|
||||
import RichGridExample from "./richGridExample/RichGridExample";
|
||||
import RichComponentsExample from "./richComponentExample/RichComponentsExample";
|
||||
import EditorComponentsExample from "./editorComponentExample/EditorComponentsExample";
|
||||
import PinnedRowComponentExample from "./pinnedRowExample/PinnedRowComponentExample";
|
||||
import FullWidthComponentExample from "./fullWidthExample/FullWidthComponentExample";
|
||||
import GroupedRowInnerRendererComponentExample from "./groupedRowInnerRendererExample/GroupedRowInnerRendererComponentExample";
|
||||
import FilterComponentExample from "./filterComponentExample/FilterComponentExample";
|
||||
import MasterDetailExample from "./masterDetailExample/MasterDetailExample";
|
||||
import SimpleReduxExample from "./simpleReduxExample/SimpleReduxExample";
|
||||
import FloatingFilterGridExample from "./floatingFilter/FloatingFilterGridExample";
|
||||
|
||||
class App extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
let searchParams = new URLSearchParams(window.location.search);
|
||||
let fromDocs = searchParams.has("fromDocs");
|
||||
let example = searchParams.has("example") ? searchParams.get("example") : 'rich-grid';
|
||||
|
||||
this.state = {
|
||||
example,
|
||||
fromDocs
|
||||
};
|
||||
|
||||
this.setExample = this.setExample.bind(this);
|
||||
}
|
||||
|
||||
setExample(example) {
|
||||
this.setState({
|
||||
example
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
let header = null;
|
||||
if (!this.state.fromDocs) {
|
||||
header = (
|
||||
<ul className="nav nav-pills">
|
||||
<li role="presentation" className={this.state.example === 'rich-grid' ? 'active' : null} onClick={() => this.setExample("rich-grid")}><a href="#">Rich Grid Example</a></li>
|
||||
<li role="presentation" className={this.state.example === 'dynamic' ? 'active' : null} onClick={() => this.setExample("dynamic")}><a href="#">Dynamic React Component Example</a></li>
|
||||
<li role="presentation" className={this.state.example === 'rich-dynamic' ? 'active' : null} onClick={() => this.setExample("rich-dynamic")}><a href="#">Dynamic React Components - Richer Example</a></li>
|
||||
<li role="presentation" className={this.state.example === 'editor' ? 'active' : null} onClick={() => this.setExample("editor")}><a href="#">Cell Editor Component Example</a></li>
|
||||
<li role="presentation" className={this.state.example === 'pinned-row' ? 'active' : null} onClick={() => this.setExample("pinned-row")}><a href="#">Pinned Row Renderer Example</a></li>
|
||||
<li role="presentation" className={this.state.example === 'full-width' ? 'active' : null} onClick={() => this.setExample("full-width")}><a href="#">Full Width Renderer Example</a></li>
|
||||
<li role="presentation" className={this.state.example === 'group-row' ? 'active' : null} onClick={() => this.setExample("group-row")}><a href="#">Grouped Row Inner Renderer Example</a></li>
|
||||
<li role="presentation" className={this.state.example === 'filter' ? 'active' : null} onClick={() => this.setExample("filter")}><a href="#">Filters Component Example</a></li>
|
||||
<li role="presentation" className={this.state.example === 'floating-filter' ? 'active' : null} onClick={() => this.setExample("floating-filter")}><a href="#">Floating Filters</a></li>
|
||||
<li role="presentation" className={this.state.example === 'master-detail' ? 'active' : null} onClick={() => this.setExample("master-detail")}><a href="#">Master Detail Example</a></li>
|
||||
<li role="presentation" className={this.state.example === 'simple-redux' ? 'active' : null} onClick={() => this.setExample("simple-redux")}><a href="#">Simple Redux Example</a></li>
|
||||
</ul>)
|
||||
}
|
||||
|
||||
let example = null;
|
||||
switch (this.state.example) {
|
||||
case 'dynamic':
|
||||
example = <DynamicComponentsExample/>;
|
||||
break;
|
||||
case 'rich-dynamic':
|
||||
example = <RichComponentsExample/>;
|
||||
break;
|
||||
case 'editor':
|
||||
example = <EditorComponentsExample/>;
|
||||
break;
|
||||
case 'pinned-row':
|
||||
example = <PinnedRowComponentExample/>;
|
||||
break;
|
||||
case 'full-width':
|
||||
example = <FullWidthComponentExample/>;
|
||||
break;
|
||||
case 'group-row':
|
||||
example = <GroupedRowInnerRendererComponentExample/>;
|
||||
break;
|
||||
case 'filter':
|
||||
example = <FilterComponentExample/>;
|
||||
break;
|
||||
case 'master-detail':
|
||||
example = <MasterDetailExample/>;
|
||||
break;
|
||||
case 'simple-redux':
|
||||
example = <SimpleReduxExample/>;
|
||||
break;
|
||||
case 'floating-filter':
|
||||
example = <FloatingFilterGridExample/>;
|
||||
break;
|
||||
default:
|
||||
example = <RichGridExample/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{header}
|
||||
{example}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default App
|
||||
19
src/dynamicComponentExample/ChildMessageRenderer.jsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React, {Component} from "react";
|
||||
|
||||
export default class ChildMessageRenderer extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.invokeParentMethod = this.invokeParentMethod.bind(this);
|
||||
}
|
||||
|
||||
invokeParentMethod() {
|
||||
this.props.context.componentParent.methodFromParent(`Row: ${this.props.node.rowIndex}, Col: ${this.props.colDef.headerName}`)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<span><button style={{height: 20}} onClick={this.invokeParentMethod}>Invoke Parent</button></span>
|
||||
);
|
||||
}
|
||||
};
|
||||
21
src/dynamicComponentExample/CubeRenderer.jsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React, {Component} from "react";
|
||||
|
||||
export default class CubeRenderer extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
value: this.valueSquared()
|
||||
};
|
||||
}
|
||||
|
||||
valueSquared() {
|
||||
return this.props.value * this.props.value * this.props.value;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<span>{this.state.value}</span>
|
||||
);
|
||||
}
|
||||
};
|
||||
17
src/dynamicComponentExample/CurrencyRenderer.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import React, {Component} from "react";
|
||||
|
||||
export default class CurrencyRenderer extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
formatValueToCurrency(currency, value) {
|
||||
return `${currency}${value}`
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<span>{this.formatValueToCurrency('EUR', this.props.value)}</span>
|
||||
);
|
||||
}
|
||||
};
|
||||
123
src/dynamicComponentExample/DynamicComponentsExample.jsx
Normal file
@@ -0,0 +1,123 @@
|
||||
import React, {Component} from "react";
|
||||
|
||||
import {AgGridReact} from "ag-grid-react";
|
||||
import SquareRenderer from "./SquareRenderer";
|
||||
import CubeRenderer from "./CubeRenderer";
|
||||
import ParamsRenderer from "./ParamsRenderer";
|
||||
import CurrencyRenderer from "./CurrencyRenderer";
|
||||
import ChildMessageRenderer from "./ChildMessageRenderer";
|
||||
|
||||
export default class DynamicComponentsExample extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
gridOptions: {
|
||||
context: {
|
||||
componentParent: this
|
||||
}
|
||||
},
|
||||
|
||||
rowData: this.createRowData(),
|
||||
|
||||
columnDefs: this.createColumnDefs()
|
||||
};
|
||||
|
||||
this.onGridReady = this.onGridReady.bind(this);
|
||||
this.refreshRowData = this.refreshRowData.bind(this);
|
||||
}
|
||||
|
||||
onGridReady(params) {
|
||||
this.gridApi = params.api;
|
||||
this.columnApi = params.columnApi;
|
||||
|
||||
this.gridApi.sizeColumnsToFit();
|
||||
}
|
||||
|
||||
onCellValueChanged($event) {
|
||||
this.gridApi.refreshCells([$event.node],["cube"]);
|
||||
}
|
||||
|
||||
methodFromParent(cell) {
|
||||
alert(`Parent Component Method from ${cell}!`);
|
||||
}
|
||||
|
||||
createColumnDefs() {
|
||||
return [
|
||||
{headerName: "Row", field: "row", width: 100},
|
||||
{
|
||||
headerName: "Square",
|
||||
field: "value",
|
||||
cellRendererFramework: SquareRenderer,
|
||||
editable:true,
|
||||
colId: "square",
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
headerName: "Cube",
|
||||
field: "value",
|
||||
cellRendererFramework: CubeRenderer,
|
||||
colId: "cube",
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
headerName: "Row Params",
|
||||
field: "row",
|
||||
cellRendererFramework: ParamsRenderer,
|
||||
colId: "params",
|
||||
width: 215
|
||||
},
|
||||
{
|
||||
headerName: "Currency",
|
||||
field: "currency",
|
||||
cellRendererFramework: CurrencyRenderer,
|
||||
colId: "params",
|
||||
width: 135
|
||||
},
|
||||
{
|
||||
headerName: "Child/Parent",
|
||||
field: "value",
|
||||
cellRendererFramework: ChildMessageRenderer,
|
||||
colId: "params",
|
||||
width: 120
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
refreshRowData() {
|
||||
let rowData = this.createRowData();
|
||||
this.gridApi.setRowData(rowData);
|
||||
}
|
||||
|
||||
createRowData() {
|
||||
let rowData = [];
|
||||
|
||||
for (let i = 0; i < 15; i++) {
|
||||
rowData.push({
|
||||
row: "Row " + i,
|
||||
value: i,
|
||||
currency: 1 + Number(Math.random()).toFixed(2)
|
||||
});
|
||||
}
|
||||
|
||||
return rowData;
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div style={{height: 400, width: 945}}
|
||||
className="ag-fresh">
|
||||
<h1>Dynamic React Component Example</h1>
|
||||
<button onClick={this.refreshRowData}>Refresh Data</button>
|
||||
<AgGridReact
|
||||
// properties
|
||||
columnDefs={this.state.columnDefs}
|
||||
rowData={this.state.rowData}
|
||||
gridOptions={this.state.gridOptions}
|
||||
|
||||
// events
|
||||
onGridReady={this.onGridReady}>
|
||||
</AgGridReact>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
13
src/dynamicComponentExample/ParamsRenderer.jsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React, {Component} from "react";
|
||||
|
||||
export default class ParamsRenderer extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<span>Field: {this.props.colDef.field}, Value: {this.props.value}</span>
|
||||
);
|
||||
}
|
||||
};
|
||||
21
src/dynamicComponentExample/SquareRenderer.jsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React, {Component} from "react";
|
||||
|
||||
export default class SquareRenderer extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
value: this.valueSquared()
|
||||
};
|
||||
}
|
||||
|
||||
valueSquared() {
|
||||
return this.props.value * this.props.value;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<span>{this.state.value}</span>
|
||||
);
|
||||
}
|
||||
};
|
||||
82
src/editorComponentExample/EditorComponentsExample.jsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import React, {Component} from "react";
|
||||
|
||||
import {AgGridReact} from "ag-grid-react";
|
||||
import MoodRenderer from "./MoodRenderer";
|
||||
import MoodEditor from "./MoodEditor";
|
||||
import NumericEditor from "./NumericEditor";
|
||||
|
||||
export default class EditorComponentsExample extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
rowData: this.createRowData(),
|
||||
columnDefs: this.createColumnDefs()
|
||||
};
|
||||
|
||||
this.onGridReady = this.onGridReady.bind(this);
|
||||
}
|
||||
|
||||
onGridReady(params) {
|
||||
this.gridApi = params.api;
|
||||
this.columnApi = params.columnApi;
|
||||
|
||||
this.gridApi.sizeColumnsToFit();
|
||||
}
|
||||
|
||||
createColumnDefs() {
|
||||
return [
|
||||
{headerName: "Name", field: "name", width: 300},
|
||||
{
|
||||
headerName: "Mood",
|
||||
field: "mood",
|
||||
cellRendererFramework: MoodRenderer,
|
||||
cellEditorFramework: MoodEditor,
|
||||
editable: true,
|
||||
width: 250
|
||||
},
|
||||
{
|
||||
headerName: "Numeric",
|
||||
field: "number",
|
||||
cellEditorFramework: NumericEditor,
|
||||
editable: true,
|
||||
width: 250
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
createRowData() {
|
||||
return [
|
||||
{name: "Bob", mood: "Happy", number: 10},
|
||||
{name: "Harry", mood: "Sad", number: 3},
|
||||
{name: "Sally", mood: "Happy", number: 20},
|
||||
{name: "Mary", mood: "Sad", number: 5},
|
||||
{name: "John", mood: "Happy", number: 15},
|
||||
{name: "Jack", mood: "Happy", number: 25},
|
||||
{name: "Sue", mood: "Sad", number: 43},
|
||||
{name: "Sean", mood: "Sad", number: 1335},
|
||||
{name: "Niall", mood: "Happy", number: 2},
|
||||
{name: "Alberto", mood: "Happy", number: 123},
|
||||
{name: "Fred", mood: "Sad", number: 532},
|
||||
{name: "Jenny", mood: "Happy", number: 34},
|
||||
{name: "Larry", mood: "Happy", number: 13},
|
||||
];
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={{height: 400, width: 945}}
|
||||
className="ag-fresh">
|
||||
<h1>Cell Editor Component Example</h1>
|
||||
<AgGridReact
|
||||
// properties
|
||||
columnDefs={this.state.columnDefs}
|
||||
rowData={this.state.rowData}
|
||||
|
||||
// events
|
||||
onGridReady={this.onGridReady}>
|
||||
</AgGridReact>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
120
src/editorComponentExample/MoodEditor.jsx
Normal file
@@ -0,0 +1,120 @@
|
||||
import React, {Component} from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
export default class MoodEditor extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onHappyClick = this.onHappyClick.bind(this);
|
||||
this.onSadClick = this.onSadClick.bind(this);
|
||||
|
||||
this.state = {
|
||||
happy: false
|
||||
}
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.setHappy(this.props.value === "Happy");
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.refs.container.addEventListener('keydown', this.checkAndToggleMoodIfLeftRight);
|
||||
this.focus();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.refs.container.removeEventListener('keydown', this.checkAndToggleMoodIfLeftRight);
|
||||
}
|
||||
|
||||
checkAndToggleMoodIfLeftRight = (event) => {
|
||||
if ([37, 39].indexOf(event.keyCode) > -1) { // left and right
|
||||
this.toggleMood();
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.focus();
|
||||
}
|
||||
|
||||
focus() {
|
||||
setTimeout(() => {
|
||||
let container = ReactDOM.findDOMNode(this.refs.container);
|
||||
if (container) {
|
||||
container.focus();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this.state.happy ? "Happy" : "Sad";
|
||||
}
|
||||
|
||||
isPopup() {
|
||||
return true;
|
||||
}
|
||||
|
||||
setHappy(happy) {
|
||||
this.setState({
|
||||
happy
|
||||
});
|
||||
}
|
||||
|
||||
onHappyClick() {
|
||||
this.setState({
|
||||
happy: true
|
||||
},
|
||||
() => this.props.api.stopEditing()
|
||||
);
|
||||
}
|
||||
|
||||
onSadClick() {
|
||||
this.setState({
|
||||
happy: false
|
||||
},
|
||||
() => this.props.api.stopEditing()
|
||||
);
|
||||
}
|
||||
|
||||
toggleMood() {
|
||||
this.setHappy(!this.state.happy);
|
||||
}
|
||||
|
||||
render() {
|
||||
let mood = {
|
||||
borderRadius: 15,
|
||||
border: "1px solid grey",
|
||||
background: "#e6e6e6",
|
||||
padding: 15,
|
||||
textAlign: "center",
|
||||
display: "inline-block"
|
||||
};
|
||||
|
||||
let unselected = {
|
||||
paddingLeft: 10,
|
||||
paddingRight: 10,
|
||||
border: "1px solid transparent",
|
||||
padding: 4
|
||||
};
|
||||
|
||||
let selected = {
|
||||
paddingLeft: 10,
|
||||
paddingRight: 10,
|
||||
border: "1px solid lightgreen",
|
||||
padding: 4
|
||||
};
|
||||
|
||||
let happyStyle = this.state.happy ? selected : unselected;
|
||||
let sadStyle = !this.state.happy ? selected : unselected;
|
||||
|
||||
return (
|
||||
<div ref="container"
|
||||
style={mood}
|
||||
tabIndex={1} // important - without this the keypresses wont be caught
|
||||
>
|
||||
<img src="images/smiley.png" onClick={this.onHappyClick} style={happyStyle}/>
|
||||
<img src="images/smiley-sad.png" onClick={this.onSadClick} style={sadStyle}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
27
src/editorComponentExample/MoodRenderer.jsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import React, {Component} from "react";
|
||||
|
||||
export default class MoodRenderer extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.setMood(this.props.value)
|
||||
}
|
||||
|
||||
refresh(params) {
|
||||
this.setMood(params.value);
|
||||
}
|
||||
|
||||
setMood(mood) {
|
||||
this.setState({
|
||||
imgForMood: mood === 'Happy' ? 'images/smiley.png' : 'images/smiley-sad.png'
|
||||
})
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<img width="20px" src={this.state.imgForMood}/>
|
||||
);
|
||||
}
|
||||
}
|
||||
96
src/editorComponentExample/NumericEditor.jsx
Normal file
@@ -0,0 +1,96 @@
|
||||
import React, {Component} from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
export default class MoodEditor extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
value: this.props.value
|
||||
};
|
||||
|
||||
this.cancelBeforeStart = this.props.charPress && ('1234567890'.indexOf(this.props.charPress) < 0);
|
||||
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.refs.input.addEventListener('keydown', this.onKeyDown);
|
||||
this.focus();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.focus();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.refs.input.removeEventListener('keydown', this.onKeyDown);
|
||||
}
|
||||
|
||||
focus() {
|
||||
setTimeout(() => {
|
||||
let container = ReactDOM.findDOMNode(this.refs.input);
|
||||
if (container) {
|
||||
container.focus();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this.state.value;
|
||||
}
|
||||
|
||||
isCancelBeforeStart() {
|
||||
return this.cancelBeforeStart;
|
||||
}
|
||||
|
||||
// will reject the number if it greater than 1,000,000
|
||||
// not very practical, but demonstrates the method.
|
||||
isCancelAfterEnd() {
|
||||
return this.state.value > 1000000;
|
||||
};
|
||||
|
||||
onKeyDown(event) {
|
||||
if(this.isLeftOrRight(event)) {
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isKeyPressedNumeric(event)) {
|
||||
if (event.preventDefault) event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
isLeftOrRight(event) {
|
||||
return [37, 39].indexOf(event.keyCode) > -1;
|
||||
}
|
||||
|
||||
handleChange(event) {
|
||||
this.setState({value: event.target.value});
|
||||
}
|
||||
|
||||
getCharCodeFromEvent(event) {
|
||||
event = event || window.event;
|
||||
return (typeof event.which === "undefined") ? event.keyCode : event.which;
|
||||
}
|
||||
|
||||
isCharNumeric(charStr) {
|
||||
return !!/\d/.test(charStr);
|
||||
}
|
||||
|
||||
isKeyPressedNumeric(event) {
|
||||
const charCode = this.getCharCodeFromEvent(event);
|
||||
const charStr = event.key ? event.key : String.fromCharCode(charCode);
|
||||
return this.isCharNumeric(charStr);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<input ref="input"
|
||||
value={this.state.value}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
86
src/filterComponentExample/FilterComponentExample.jsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import React, {Component} from "react";
|
||||
|
||||
import {AgGridReact} from "ag-grid-react";
|
||||
|
||||
import "ag-grid-enterprise";
|
||||
|
||||
import PartialMatchFilter from "./PartialMatchFilter";
|
||||
|
||||
export default class FilterComponentExample extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
gridOptions: {},
|
||||
|
||||
rowData: this.createRowData(),
|
||||
columnDefs: this.createColumnDefs(),
|
||||
};
|
||||
|
||||
this.onGridReady = this.onGridReady.bind(this);
|
||||
this.onClicked = this.onClicked.bind(this);
|
||||
}
|
||||
|
||||
onGridReady(params) {
|
||||
this.gridApi = params.api;
|
||||
this.columnApi = params.columnApi;
|
||||
|
||||
this.gridApi.sizeColumnsToFit();
|
||||
}
|
||||
|
||||
onClicked() {
|
||||
this.gridApi.getFilterInstance("name").getFrameworkComponentInstance().componentMethod("Hello World!");
|
||||
}
|
||||
|
||||
createColumnDefs() {
|
||||
return [
|
||||
{headerName: "Row", field: "row", width: 400},
|
||||
{
|
||||
headerName: "Filter Component",
|
||||
field: "name",
|
||||
filterFramework: PartialMatchFilter,
|
||||
width: 400,
|
||||
menuTabs:['filterMenuTab']
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
createRowData() {
|
||||
return [
|
||||
{"row": "Row 1", "name": "Michael Phelps"},
|
||||
{"row": "Row 2", "name": "Natalie Coughlin"},
|
||||
{"row": "Row 3", "name": "Aleksey Nemov"},
|
||||
{"row": "Row 4", "name": "Alicia Coutts"},
|
||||
{"row": "Row 5", "name": "Missy Franklin"},
|
||||
{"row": "Row 6", "name": "Ryan Lochte"},
|
||||
{"row": "Row 7", "name": "Allison Schmitt"},
|
||||
{"row": "Row 8", "name": "Natalie Coughlin"},
|
||||
{"row": "Row 9", "name": "Ian Thorpe"},
|
||||
{"row": "Row 10", "name": "Bob Mill"},
|
||||
{"row": "Row 11", "name": "Willy Walsh"},
|
||||
{"row": "Row 12", "name": "Sarah McCoy"},
|
||||
{"row": "Row 13", "name": "Jane Jack"},
|
||||
{"row": "Row 14", "name": "Tina Wills"}
|
||||
];
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={{height: 400, width: 945}}
|
||||
className="ag-fresh">
|
||||
<h1>Filter Component Example</h1>
|
||||
<button style={{marginBottom: 10}} onClick={this.onClicked}>Filter Instance Method</button>
|
||||
<AgGridReact
|
||||
// properties
|
||||
columnDefs={this.state.columnDefs}
|
||||
rowData={this.state.rowData}
|
||||
|
||||
enableFilter
|
||||
|
||||
// events
|
||||
onGridReady={this.onGridReady}>
|
||||
</AgGridReact>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
70
src/filterComponentExample/PartialMatchFilter.jsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import React, {Component} from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
export default class PartialMatchFilter extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
text: ''
|
||||
}
|
||||
this.valueGetter = this.props.valueGetter;
|
||||
|
||||
this.onChange = this.onChange.bind(this);
|
||||
}
|
||||
|
||||
isFilterActive() {
|
||||
return this.state.text !== null && this.state.text !== undefined && this.state.text !== '';
|
||||
}
|
||||
|
||||
doesFilterPass(params) {
|
||||
return this.state.text.toLowerCase()
|
||||
.split(" ")
|
||||
.every((filterWord) => {
|
||||
return this.valueGetter(params.node).toString().toLowerCase().indexOf(filterWord) >= 0;
|
||||
});
|
||||
}
|
||||
|
||||
getModel() {
|
||||
return {value: this.state.text};
|
||||
}
|
||||
|
||||
setModel(model) {
|
||||
this.state.text = model ? model.value : '';
|
||||
}
|
||||
|
||||
afterGuiAttached(params) {
|
||||
this.focus();
|
||||
}
|
||||
|
||||
focus() {
|
||||
setTimeout(() => {
|
||||
let container = ReactDOM.findDOMNode(this.refs.input);
|
||||
if (container) {
|
||||
container.focus();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
componentMethod(message) {
|
||||
alert(`Alert from PartialMatchFilterComponent ${message}`);
|
||||
}
|
||||
|
||||
onChange(event) {
|
||||
let newValue = event.target.value;
|
||||
if (this.state.text !== newValue) {
|
||||
this.setState({
|
||||
text: newValue
|
||||
}, () => {
|
||||
this.props.filterChangedCallback();
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<span>Filter: <input style={{height: "20px"}} ref="input" value={this.state.text} onChange={this.onChange}/></span>
|
||||
);
|
||||
}
|
||||
};
|
||||
69
src/floatingFilter/FloatingFilterGridExample.jsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import React, {Component} from "react";
|
||||
import {AgGridReact} from "ag-grid-react";
|
||||
import FloatingFilter from "./floatingFilter";
|
||||
import overrideStyle from "./floatingFilter.css";
|
||||
|
||||
export default class FloatingFilterGridExample extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
columnDefs: this.createColumnDefs(),
|
||||
rowData: this.createRowData()
|
||||
}
|
||||
}
|
||||
|
||||
onGridReady(params) {
|
||||
this.gridApi = params.api;
|
||||
this.columnApi = params.columnApi;
|
||||
|
||||
this.gridApi.sizeColumnsToFit();
|
||||
}
|
||||
|
||||
createColumnDefs() {
|
||||
return [
|
||||
{
|
||||
headerName: "Make",
|
||||
field: "make",
|
||||
floatingFilterComponentFramework: FloatingFilter,
|
||||
filter: 'set'
|
||||
},
|
||||
{headerName: "Model", field: "model"},
|
||||
{headerName: "Price", field: "price"}
|
||||
];
|
||||
}
|
||||
|
||||
createRowData() {
|
||||
return [
|
||||
{make: "Toyota", model: "Celica", price: 35000},
|
||||
{make: "Ford", model: "Mondeo", price: 32000},
|
||||
{make: "Porsche", model: "Boxter", price: 72000}
|
||||
];
|
||||
}
|
||||
|
||||
render() {
|
||||
let divStyle = {
|
||||
height: 400,
|
||||
width: 945
|
||||
};
|
||||
|
||||
// combine the styles
|
||||
const style = Object.assign({}, divStyle, overrideStyle);
|
||||
|
||||
return (
|
||||
<div style={style} className="ag-fresh">
|
||||
<h1>Floating Filter Example</h1>
|
||||
<AgGridReact
|
||||
// properties
|
||||
columnDefs={this.state.columnDefs}
|
||||
rowData={this.state.rowData}
|
||||
|
||||
floatingFilter={true}
|
||||
|
||||
// events
|
||||
onGridReady={this.onGridReady}>
|
||||
</AgGridReact>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
};
|
||||
3
src/floatingFilter/floatingFilter.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.ag-floating-filter-button button {
|
||||
margin: 0
|
||||
}
|
||||
39
src/floatingFilter/floatingFilter.jsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import React, {Component} from "react";
|
||||
|
||||
export default class FloatingFilter extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
parentModel: null
|
||||
}
|
||||
}
|
||||
|
||||
// when does this get called? how to test this?
|
||||
onParentModelChanged(parentModel) {
|
||||
this.setState({
|
||||
parentModel: parentModel
|
||||
})
|
||||
}
|
||||
|
||||
remove(item) {
|
||||
this.props.onFloatingFilterChanged({
|
||||
model: this.state.parentModel.filter(it => it !== item)
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.state.parentModel) return null;
|
||||
|
||||
// as the backing filter is a set filter what we're doing here is rendering the list in the set
|
||||
// and when the [x] removing the selected item, thereby effectively hiding it
|
||||
let options = this.state.parentModel.map((item, i) => {
|
||||
let removeMeListener = () => {
|
||||
this.remove(item)
|
||||
};
|
||||
let removeMeElement = <a onClick={removeMeListener}>[x]</a>;
|
||||
return <span key={i}>{item}{removeMeElement}</span>;
|
||||
});
|
||||
|
||||
return <div>{options}</div>;
|
||||
}
|
||||
}
|
||||
88
src/fullWidthExample/FullWidthComponentExample.jsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import React, {Component} from "react";
|
||||
|
||||
import {AgGridReact} from "ag-grid-react";
|
||||
import NameAndAgeRenderer from "./NameAndAgeRenderer";
|
||||
|
||||
export default class FullWidthComponentExample extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
gridOptions: {},
|
||||
|
||||
rowData: this.createRowData(),
|
||||
columnDefs: this.createColumnDefs()
|
||||
};
|
||||
|
||||
this.onGridReady = this.onGridReady.bind(this);
|
||||
}
|
||||
|
||||
onGridReady(params) {
|
||||
this.gridApi = params.api;
|
||||
this.columnApi = params.columnApi;
|
||||
|
||||
this.gridApi.sizeColumnsToFit();
|
||||
}
|
||||
|
||||
isFullWidthCell(rowNode) {
|
||||
return (rowNode.id === "0") || (parseInt(rowNode.id) % 2 === 0);
|
||||
}
|
||||
|
||||
createColumnDefs() {
|
||||
return [
|
||||
{
|
||||
headerName: "Name",
|
||||
field: "name",
|
||||
width: 400
|
||||
},
|
||||
{
|
||||
headerName: "Age",
|
||||
field: "age",
|
||||
width: 399
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
createRowData() {
|
||||
return [
|
||||
{name: "Bob", age: 10},
|
||||
{name: "Harry", age: 3},
|
||||
{name: "Sally", age: 20},
|
||||
{name: "Mary", age: 5},
|
||||
{name: "John", age: 15},
|
||||
{name: "Bob", age: 10},
|
||||
{name: "Harry", age: 3},
|
||||
{name: "Sally", age: 20},
|
||||
{name: "Mary", age: 5},
|
||||
{name: "John", age: 15},
|
||||
{name: "Jack", age: 25},
|
||||
{name: "Sue", age: 43},
|
||||
{name: "Sean", age: 44},
|
||||
{name: "Niall", age: 2},
|
||||
{name: "Alberto", age: 32},
|
||||
{name: "Fred", age: 53},
|
||||
{name: "Jenny", age: 34},
|
||||
{name: "Larry", age: 13},
|
||||
];
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={{height: 400, width: 945}}
|
||||
className="ag-fresh">
|
||||
<h1>Full Width Renderer Example</h1>
|
||||
<AgGridReact
|
||||
// properties
|
||||
columnDefs={this.state.columnDefs}
|
||||
rowData={this.state.rowData}
|
||||
|
||||
isFullWidthCell={this.isFullWidthCell}
|
||||
fullWidthCellRendererFramework={NameAndAgeRenderer}
|
||||
|
||||
// events
|
||||
onGridReady={this.onGridReady}>
|
||||
</AgGridReact>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
15
src/fullWidthExample/NameAndAgeRenderer.jsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import React, {Component} from "react";
|
||||
|
||||
export default class NameAndAgeRenderer extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.values = `Name: ${this.props.data.name}, Age: ${this.props.data.age}`;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<span>Full Width Column! { this.values }</span>
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,126 @@
|
||||
import React, {Component} from "react";
|
||||
|
||||
import {AgGridReact} from "ag-grid-react";
|
||||
import MedalRenderer from "./MedalRenderer";
|
||||
|
||||
import "ag-grid-enterprise";
|
||||
|
||||
export default class GroupedRowInnerRendererComponentExample extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
gridOptions: {},
|
||||
|
||||
rowData: this.createRowData(),
|
||||
columnDefs: this.createColumnDefs(),
|
||||
};
|
||||
|
||||
this.onGridReady = this.onGridReady.bind(this);
|
||||
}
|
||||
|
||||
onGridReady(params) {
|
||||
this.gridApi = params.api;
|
||||
this.columnApi = params.columnApi;
|
||||
|
||||
this.gridApi.sizeColumnsToFit();
|
||||
}
|
||||
|
||||
createColumnDefs() {
|
||||
return [
|
||||
{
|
||||
headerName: "Country",
|
||||
field: "country",
|
||||
width: 100,
|
||||
rowGroupIndex: 0
|
||||
},
|
||||
{
|
||||
headerName: "Name",
|
||||
field: "name",
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
headerName: "Gold",
|
||||
field: "gold",
|
||||
width: 100,
|
||||
aggFunc: 'sum'
|
||||
},
|
||||
{
|
||||
headerName: "Silver",
|
||||
field: "silver",
|
||||
width: 100,
|
||||
aggFunc: 'sum'
|
||||
},
|
||||
{
|
||||
headerName: "Bronze",
|
||||
field: "bronze",
|
||||
width: 100,
|
||||
aggFunc: 'sum'
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
createRowData() {
|
||||
return [
|
||||
{country: "United States", name: "Bob", gold: 1, silver: 0, bronze: 0},
|
||||
{country: "United States", name: "Jack", gold: 0, silver: 1, bronze: 1},
|
||||
{country: "United States", name: "Sue", gold: 1, silver: 0, bronze: 1},
|
||||
{country: "United Kingdom", name: "Mary", gold: 1, silver: 1, bronze: 0},
|
||||
{country: "United Kingdom", name: "Tess", gold: 0, silver: 1, bronze: 1},
|
||||
{country: "United Kingdom", name: "John", gold: 0, silver: 2, bronze: 1},
|
||||
{country: "Jamaica", name: "Bob", gold: 1, silver: 1, bronze: 0},
|
||||
{country: "Jamaica", name: "Jack", gold: 1, silver: 1, bronze: 0},
|
||||
{country: "Jamaica", name: "Mary", gold: 1, silver: 1, bronze: 0},
|
||||
{country: "South Africa", name: "Bob", gold: 1, silver: 0, bronze: 1},
|
||||
{country: "South Africa", name: "Jack", gold: 1, silver: 0, bronze: 1},
|
||||
{country: "South Africa", name: "Mary", gold: 1, silver: 0, bronze: 1},
|
||||
{country: "South Africa", name: "John", gold: 1, silver: 0, bronze: 1},
|
||||
{country: "New Zealand", name: "Bob", gold: 1, silver: 0, bronze: 0},
|
||||
{country: "New Zealand", name: "Jack", gold: 0, silver: 1, bronze: 1},
|
||||
{country: "New Zealand", name: "Sue", gold: 1, silver: 0, bronze: 1},
|
||||
{country: "Australia", name: "Mary", gold: 1, silver: 1, bronze: 0},
|
||||
{country: "Australia", name: "Tess", gold: 0, silver: 1, bronze: 1},
|
||||
{country: "Australia", name: "John", gold: 0, silver: 2, bronze: 1},
|
||||
{country: "Canada", name: "Bob", gold: 1, silver: 1, bronze: 0},
|
||||
{country: "Canada", name: "Jack", gold: 1, silver: 1, bronze: 0},
|
||||
{country: "Canada", name: "Mary", gold: 1, silver: 1, bronze: 0},
|
||||
{country: "Switzerland", name: "Bob", gold: 1, silver: 0, bronze: 1},
|
||||
{country: "Switzerland", name: "Jack", gold: 1, silver: 0, bronze: 1},
|
||||
{country: "Switzerland", name: "Mary", gold: 1, silver: 0, bronze: 1},
|
||||
{country: "Switzerland", name: "John", gold: 1, silver: 0, bronze: 1},
|
||||
{country: "Spain", name: "Bob", gold: 1, silver: 0, bronze: 0},
|
||||
{country: "Spain", name: "Jack", gold: 0, silver: 1, bronze: 1},
|
||||
{country: "Spain", name: "Sue", gold: 1, silver: 0, bronze: 1},
|
||||
{country: "Portugal", name: "Mary", gold: 1, silver: 1, bronze: 0},
|
||||
{country: "Portugal", name: "Tess", gold: 0, silver: 1, bronze: 1},
|
||||
{country: "Portugal", name: "John", gold: 0, silver: 2, bronze: 1},
|
||||
{country: "Zimbabwe", name: "Bob", gold: 1, silver: 1, bronze: 0},
|
||||
{country: "Zimbabwe", name: "Jack", gold: 1, silver: 1, bronze: 0},
|
||||
{country: "Zimbabwe", name: "Mary", gold: 1, silver: 1, bronze: 0},
|
||||
{country: "Brazil", name: "Bob", gold: 1, silver: 0, bronze: 1},
|
||||
{country: "Brazil", name: "Jack", gold: 1, silver: 0, bronze: 1},
|
||||
{country: "Brazil", name: "Mary", gold: 1, silver: 0, bronze: 1},
|
||||
{country: "Brazil", name: "John", gold: 1, silver: 0, bronze: 1}
|
||||
];
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={{height: 400, width: 945}}
|
||||
className="ag-fresh">
|
||||
<h1>Group Row Renderer Example</h1>
|
||||
<AgGridReact
|
||||
// properties
|
||||
columnDefs={this.state.columnDefs}
|
||||
rowData={this.state.rowData}
|
||||
|
||||
groupUseEntireRow
|
||||
groupRowInnerRendererFramework={MedalRenderer}
|
||||
|
||||
// events
|
||||
onGridReady={this.onGridReady}>
|
||||
</AgGridReact>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
21
src/groupedRowInnerRendererExample/MedalRenderer.jsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React, {Component} from "react";
|
||||
|
||||
export default class MedalRenderer extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.country = this.props.node.key;
|
||||
this.gold = this.props.node.aggData.gold;
|
||||
this.silver = this.props.node.aggData.silver;
|
||||
this.bronze = this.props.node.aggData.bronze;
|
||||
|
||||
// override the containing div so that the +/- and label are inline
|
||||
this.props.reactContainer.style.display = "inline-block";
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<span>{this.country} Gold: {this.gold}, Silver: {this.silver}, Bronze: {this.bronze}</span>
|
||||
);
|
||||
}
|
||||
};
|
||||
BIN
src/images/alberto.png
Normal file
|
After Width: | Height: | Size: 69 KiB |
BIN
src/images/favicon.ico
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/images/fire.png
Normal file
|
After Width: | Height: | Size: 606 B |
BIN
src/images/flags/ar.png
Normal file
|
After Width: | Height: | Size: 128 B |
BIN
src/images/flags/br.png
Normal file
|
After Width: | Height: | Size: 256 B |
BIN
src/images/flags/co.png
Normal file
|
After Width: | Height: | Size: 99 B |
BIN
src/images/flags/de.png
Normal file
|
After Width: | Height: | Size: 99 B |
BIN
src/images/flags/es.png
Normal file
|
After Width: | Height: | Size: 174 B |
BIN
src/images/flags/fr.png
Normal file
|
After Width: | Height: | Size: 94 B |
BIN
src/images/flags/gb.png
Normal file
|
After Width: | Height: | Size: 289 B |
BIN
src/images/flags/gr.png
Normal file
|
After Width: | Height: | Size: 228 B |
BIN
src/images/flags/ie.png
Normal file
|
After Width: | Height: | Size: 119 B |
BIN
src/images/flags/is.png
Normal file
|
After Width: | Height: | Size: 154 B |
BIN
src/images/flags/it.png
Normal file
|
After Width: | Height: | Size: 94 B |
BIN
src/images/flags/mt.png
Normal file
|
After Width: | Height: | Size: 136 B |
BIN
src/images/flags/no.png
Normal file
|
After Width: | Height: | Size: 154 B |
BIN
src/images/flags/pe.png
Normal file
|
After Width: | Height: | Size: 89 B |
BIN
src/images/flags/pt.png
Normal file
|
After Width: | Height: | Size: 227 B |
BIN
src/images/flags/se.png
Normal file
|
After Width: | Height: | Size: 97 B |
BIN
src/images/flags/uy.png
Normal file
|
After Width: | Height: | Size: 222 B |
BIN
src/images/flags/ve.png
Normal file
|
After Width: | Height: | Size: 173 B |
BIN
src/images/frost.png
Normal file
|
After Width: | Height: | Size: 739 B |