Compare commits

...

78 Commits

Author SHA1 Message Date
Alberto
32142b435b v12 Hot fixes 2017-07-21 12:10:00 +02:00
Alberto
d71781899d AG-618 Fixing floating filter issue. Also allowing for gulp watch of Angular and React 2017-07-20 13:59:16 +02:00
Sean Landsman
9560f86bf5 Updated readme 2017-07-19 10:30:28 +01:00
Alberto
9f4b270884 New release branch, everything compiles 2017-07-13 11:25:34 +01:00
John Masterson
e5627d8b9f Added links to ReadMe 2017-07-11 17:07:57 +01:00
Alberto
f8f0c21af2 Version 11.0.0 2017-06-26 16:40:27 +02:00
Sean Landsman
b54d549959 AG-528 Document and use in examples overriding of style in child (react component) divs 2017-06-14 15:32:09 +01:00
Sean Landsman
464a0ea9f4 AG-529 Implement improvements - esp around property changes 2017-06-14 13:15:54 +01:00
Sean Landsman
675cbe497c AG-538 Upgrade to React 15x
AG-539 React rich grid example - fix bugs
2017-06-13 18:15:05 +01:00
Sean Landsman
a4dbbf529d AG-534 Make use of new data retrieval & updating in trader dashboard 2017-06-13 15:17:30 +01:00
Alberto
89d36b0bf4 version 10.1.0 2017-06-08 10:56:03 +01:00
Alberto
9ff1a54e57 Merge branch 'latest' into 10.1.0
# Conflicts:
#	package.json
2017-06-08 10:31:06 +01:00
Sean Landsman
7ed2666aaf AG-420 Improve React implementation 2017-06-06 09:42:17 +01:00
Sean Landsman
76258a8ee4 AG-420 Improve React implementation 2017-06-06 09:39:10 +01:00
Sean Landsman
7564f1597a AG-420 Improve React implementation 2017-06-06 08:52:37 +01:00
Sean Landsman
5f42824c5e Fix dep issue 2017-06-01 20:52:37 +02:00
Sean Landsman
ef44ebc166 AG-420 Improve React implementation 2017-05-26 14:50:49 +01:00
Sean Landsman
9c3c7a77c0 AG-420 Improve React implementation 2017-05-26 14:49:34 +01:00
Sean Landsman
f8b8a0b066 AG-420 Improve React implementation 2017-05-26 11:12:43 +01:00
Sean Landsman
e1871affe3 AG-420 Improve React implementation 2017-05-26 10:31:12 +01:00
Sean Landsman
3226c0d6ba AG-420 Improve React implementation 2017-05-25 22:15:56 +01:00
Sean Landsman
1b3fc21107 AG-420 Improve React implementation 2017-05-25 19:02:14 +01:00
Sean Landsman
587af2e888 AG-420 Improve React implementation 2017-05-25 12:21:02 +01:00
Sean Landsman
4ef1959a52 AG-420 Improve React implementation 2017-05-24 14:28:07 +01:00
Sean Landsman
ddefba039a AG-420 Improve React implementation 2017-05-23 17:51:52 +01:00
Sean Landsman
06ebb8bf18 AG-420 Improve React implementation 2017-05-23 17:51:33 +01:00
Sean Landsman
e183a0503a AG-420 Improve React implementation 2017-05-23 15:10:27 +01:00
Sean Landsman
19771949cb AG-420 Improve React implementation 2017-05-23 11:43:35 +01:00
Alberto
5fbdb690af There is a dependency (to be eliminated) in the release script and the order of the projects in the package.json 2017-05-22 15:06:18 +01:00
Sean Landsman
93fd0fe112 AG-420 Improve React implementation 2017-05-22 15:03:35 +01:00
Sean Landsman
a8163cad16 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	package.json
2017-05-22 15:02:25 +01:00
Sean Landsman
3f54c23b4d AG-420 Improve React implementation 2017-05-22 15:01:21 +01:00
Alberto
00dd86f4c8 Merge branch '10.0.0' 2017-05-22 10:37:03 +01:00
Alberto
e8ba0413ae Version 10.0.0 2017-05-22 10:32:54 +01:00
Sean Landsman
a3a0336f6b AG-420 Improve React implementation 2017-05-19 15:58:12 +01:00
Sean Landsman
a438cb6169 AG-420 Improve React implementation 2017-05-19 11:28:58 +01:00
Sean Landsman
8677118a27 AG-420 Improve React implementation 2017-05-17 13:22:59 +01:00
Alberto
4069f6bc10 Merge branch '9.1.0' 2017-04-27 15:20:38 +02:00
Alberto
575edb5b85 Release 9.1.0 2017-04-27 15:18:12 +02:00
Alberto
042dfc62a6 Merge branch '9.0.0' 2017-03-29 17:13:06 +01:00
Alberto
d59b5e8071 Version 9.0.0 2017-03-29 17:11:37 +01:00
Alberto
afa03227e4 Full width renderer with react 2017-03-09 11:03:20 +01:00
Alberto
a2952230d0 Starting work of full width Renderer with React 2017-03-08 16:46:50 +01:00
Alberto
7f8d1bb3a7 Starting work of full width Renderer with React 2017-03-08 16:36:05 +01:00
Alberto
801bf16339 Merge branch '8.2.0' 2017-03-08 15:12:51 +01:00
Alberto
cf1be920ee Release 8.2.0 2017-03-08 14:43:47 +01:00
Sean Landsman
f8ad31c2e0 more docs work 2017-02-24 15:09:48 +00:00
Alberto
e026fb65e3 Merge branch '8.1.0' 2017-02-20 13:11:50 +00:00
Alberto
05a7371467 8.1.0 2017-02-20 12:49:05 +00:00
Alberto
4ea0b7f8b2 Merge branch '8.0.0' 2017-02-09 15:48:33 +00:00
Alberto
5904f47d55 8.0.0 2017-02-09 14:14:07 +00:00
Alberto
25b416b71d Getting ready for releasing 8.0.0 2017-02-09 13:59:26 +00:00
Alberto
abb7828921 Getting ready for releasing 8.0.0 2017-02-09 13:58:19 +00:00
Alberto
ad5fd2d1ce ag-49 Ng2 Header component 2017-02-07 10:00:52 +00:00
Alberto
6d686f1c02 ag-49 Custom Group Header example completed 2017-02-03 11:16:17 +00:00
Alberto
6376a24e3a ag-49 Custom Header example completed 2017-02-03 10:14:20 +00:00
Sean Landsman
9c8cf08d2a AG-195 Create react example using grouped data 2017-01-26 11:40:46 +00:00
Alberto
a8c70c0ae8 Adding the cellRenderer so the example and updating the docs to reflect the example changes 2017-01-23 14:58:26 +00:00
Alberto
3390411e8a React date component, polishing the UI and adding documentation 2017-01-23 12:30:15 +00:00
Alberto
a280e97763 Creating React component to manage the dates in the DateFilter 2017-01-20 15:58:01 +00:00
Alberto
5451b02acd Creating the scaffolding for the ag-grid-react DateComponent 2017-01-20 15:29:42 +00:00
Alberto
3613a26f48 Creating the scaffolding for the ag-grid-react DateComponent 2017-01-20 14:47:11 +00:00
Sean Landsman
4acdee4b72 AG-24 Add getFilterInstance to React Examples Project 2017-01-19 10:58:32 +00:00
Alberto
72dae24ebe 7.2.0 2017-01-17 17:05:38 +00:00
ceolter
0aa2f7a2be Merge remote-tracking branch 'origin/master' 2017-01-03 14:13:12 +00:00
ceolter
6a94aee204 fixed example, the percent bar was not correct height 2017-01-03 14:13:03 +00:00
Sean Landsman
e0dfa732c7 7.1.1 2016-12-22 15:16:58 +00:00
Sean Landsman
d26876876f 7.0.0 2016-11-30 16:41:47 +00:00
Sean Landsman
3148c047b6 6.4.0 2016-11-11 13:42:22 +00:00
seanlandsman
33b006b783 6.3.0 2016-11-04 15:45:15 +00:00
seanlandsman
3b5ee65790 Release of 6.2.0 2016-10-11 16:13:26 +01:00
Sean Landsman
4829b2f6cd Release of 6.1.0 2016-10-03 13:02:15 +01:00
Sean Landsman
67af64ea9d Release of 6.0.1 2016-09-15 15:05:56 +01:00
ceolter
99dfa1e4f9 new component model 2016-09-14 15:58:15 +01:00
ceolter
1e9ef53316 new component model 2016-09-14 15:02:35 +01:00
ceolter
b5949d8d5d new component model 2016-09-14 12:15:30 +01:00
ceolter
b4b8c85172 new component model 2016-09-12 22:20:20 +01:00
ceolter
c703eaf89c new component model 2016-09-12 13:45:37 +01:00
143 changed files with 13236 additions and 209 deletions

View File

@@ -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

BIN
images/alberto.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

BIN
images/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
images/fire.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

BIN
images/flags/ar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 B

BIN
images/flags/br.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 B

BIN
images/flags/co.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 B

BIN
images/flags/de.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 B

BIN
images/flags/es.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

BIN
images/flags/fr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 B

BIN
images/flags/gb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 B

BIN
images/flags/gr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

BIN
images/flags/ie.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

BIN
images/flags/is.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

BIN
images/flags/it.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 B

BIN
images/flags/mt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B

BIN
images/flags/no.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

BIN
images/flags/pe.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 B

BIN
images/flags/pt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 B

BIN
images/flags/se.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 B

BIN
images/flags/uy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 B

BIN
images/flags/ve.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 B

BIN
images/frost.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 739 B

BIN
images/horse.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
images/niall.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

BIN
images/phone.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
images/sean.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
images/smiley-sad.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
images/smiley.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
images/statue.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
images/sun.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 B

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,24 @@
{
"name": "ag-grid-react-example",
"version": "5.4.0",
"version": "12.0.0",
"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-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/react-examples/examples && ncp dist/trader ../ag-grid-docs/src/react-examples/trader",
"build-to-docs": "npm run build && npm run copy-to-docs",
"start": "npm run examples"
},
"repository": {
"type": "git",
@@ -24,19 +37,35 @@
},
"homepage": "http://www.ag-grid.com/",
"devDependencies": {
"babel-loader": "6.2.1",
"babel-preset-es2015": "6.3.13",
"babel-preset-react": "6.3.13",
"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",
"prop-types": "15.5.x",
"css-loader": "0.23.x",
"mkdirp": "0.5.1",
"ncp": "2.0.0",
"rimraf": "2.5.x",
"style-loader": "0.13.x",
"webpack": "1.12.x",
"webpack-dev-server": "1.14.x"
},
"dependencies": {
"react": "0.14.6",
"react-dom": "0.14.6",
"ag-grid": "~5.4.0",
"ag-grid-enterprise": "~5.4.0",
"ag-grid-react": "~5.4.0"
"bootstrap": "3.3.7",
"d3": "4.9.1",
"file-loader": "0.11.1",
"lodash": "4.17.4",
"react": "15.5.x",
"react-dom": "15.5.x",
"react-redux": "5.0.x",
"redux": "3.6.x",
"url-search-params-polyfill": "1.2.0",
"ag-grid": "12.0.x",
"ag-grid-enterprise": "12.0.x",
"ag-grid-react": "12.0.x"
}
}

View File

@@ -48,15 +48,11 @@ export default class MyApp extends React.Component {
createColumnDefs() {
var columnDefs = [];
// pass in the parent component to use for React component to the cellRenderer.
// this is optional. if missing, then react router will not work.
var cellRenderer = reactCellRendererFactory(SimpleCellRenderer, this);
this.columnNames.forEach( colName => {
columnDefs.push({
headerName: colName.toUpperCase(),
field: colName,
cellRenderer: cellRenderer,
cellRendererFramework: SimpleCellRenderer,
width: 100
});
});

View File

@@ -1,17 +1,16 @@
import React from 'react';
import * as PropTypes from 'prop-types';
export default class SimpleCellRenderer extends React.Component {
render() {
var params = this.props.params;
// the class below does nothing, it's just for testing, so we can inspect the dom of
// the result and looking for it, to validate that this cellRenderer is actually getting used.
return (
<span className="simple-cell-renderer">{params.value}</span>
<span className="simple-cell-renderer">{this.props.value}</span>
);
}
}
SimpleCellRenderer.propTypes = {
params: React.PropTypes.object
params: PropTypes.object
};

View File

@@ -1,67 +0,0 @@
import SkillsCellRenderer from './SkillsCellRenderer.jsx';
import ProficiencyCellRenderer from './ProficiencyCellRenderer.jsx';
import RefData from './RefData';
import {reactCellRendererFactory} from 'ag-grid-react';
import {reactFilterFactory} from 'ag-grid-react';
import SkillsFilter from './SkillsFilter.jsx';
import ProficiencyFilter from './ProficiencyFilter.jsx';
export default class ColDefFactory {
createColDefs(parentReactComponent) {
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},
{headerName: "Country", field: "country", width: 150, enableRowGroup: true, enablePivot: true,
// not bothering with React for country, as it's a simple HTML string
cellRenderer: countryCellRenderer, pinned: true,
filterParams: {cellRenderer: countryCellRenderer, cellHeight: 20}},
]
},
{
headerName: 'IT Skills',
children: [
{headerName: "Skills", width: 125, suppressSorting: true, field: 'skills', enableRowGroup: true, enablePivot: true,
// using ag-Grid's React cellRenderer factory
cellRenderer: reactCellRendererFactory(SkillsCellRenderer, parentReactComponent),
// using ag-Grid's React filter factory
filter: reactFilterFactory(SkillsFilter, parentReactComponent)
},
{headerName: "Proficiency", field: "proficiency", filter: 'number', width: 120, enableValue: true,
// using ag-Grid's React cellRenderer factory
cellRenderer: reactCellRendererFactory(ProficiencyCellRenderer, parentReactComponent),
// using ag-Grid's React filter factory
filter: reactFilterFactory(ProficiencyFilter, parentReactComponent)}
]
},
{
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;
}
}

View File

@@ -1,34 +0,0 @@
import React from 'react';
import RefData from './RefData';
// cell renderer for the proficiency column. this is a very basic cell renderer,
// it is arguable that we should not of used React and just returned a string of
// html as a normal ag-Grid cellRenderer.
export default class ProficiencyCellRenderer extends React.Component {
render() {
var params = this.props.params;
var backgroundColor;
if (params.value < 20) {
backgroundColor = 'red';
} else if (params.value < 60) {
backgroundColor = '#ff9900';
} else {
backgroundColor = '#00A000';
}
return (
<div className="div-percent-bar" style={{ width: params.value + '%', backgroundColor: backgroundColor }}>
<div className="div-percent-value">{params.value}%</div>
</div>
);
}
}
// the grid will always pass in one props called 'params',
// which is the grid passing you the params for the cellRenderer.
// this piece is optional. the grid will always pass the 'params'
// props, so little need for adding this validation meta-data.
ProficiencyCellRenderer.propTypes = {
params: React.PropTypes.object
};

View File

@@ -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
);
});

View File

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

View File

@@ -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>
);
}
};

View File

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

View File

@@ -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);

View 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',
cellFormatter: this.numberFormatter,
cellRenderer: 'animateShowChange',
cellStyle: {'text-align': 'right'}
},
{
field: 'bid',
headerName: 'Bid',
cellFormatter: this.numberFormatter,
cellRenderer: 'animateShowChange',
cellStyle: {'text-align': 'right'}
},
{
field: 'ask',
headerName: 'Ask',
cellFormatter: 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>
);
}
}

View 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>
);
}
}
}

View 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>
);
}
}
}

View 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>
);
}
};

View 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>
);
}
}
}

View 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>
);
}
}
}

View 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>
);
}
}
}

View 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);

View 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>
);
}
};

View File

@@ -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
};

View 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>

View 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')
);
});

View File

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

View File

@@ -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}
];

View 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]
];

View 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
View 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 FloatingRowComponentExample from "./floatingRowExample/FloatingRowComponentExample";
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 SimpleGridExample from "./floatingFilter/SimpleGridExample";
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 === 'floating-row' ? 'active' : null} onClick={() => this.setExample("floating-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 'floating-row':
example = <FloatingRowComponentExample/>;
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 = <SimpleGridExample/>;
break;
default:
example = <RichGridExample/>;
}
return (
<div>
{header}
{example}
</div>
)
}
}
export default App

View 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>
);
}
};

View 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>
);
}
};

View 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>
);
}
};

View 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>
);
}
};

View 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>
);
}
};

View 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>
);
}
};

View 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>
);
}
};

View File

@@ -0,0 +1,107 @@
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.focus();
}
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",
// outline: "none"
};
let unselected = {
paddingLleft: 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.value === 'Happy' ? selected : unselected;
let sadStyle = this.state.value !== 'Happy' ? selected : unselected;
return (
<div ref="container"
style={mood}>
<img src="images/smiley.png" onClick={this.onHappyClick} style={happyStyle}/>
<img src="images/smiley-sad.png" onClick={this.onSadClick} style={sadStyle}/>
</div>
);
}
}

View 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}/>
);
}
}

View File

@@ -0,0 +1,83 @@
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.focus();
}
componentDidUpdate() {
this.focus();
}
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.isKeyPressedNumeric(event)) {
if (event.preventDefault) event.preventDefault();
}
}
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}
onKeyDown={this.onKeyDown}
onChange={this.handleChange}
/>
);
}
}

View 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>
);
}
};

View 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>
);
}
};

View File

@@ -0,0 +1,60 @@
import React, {Component} from "react";
import {AgGridReact} from "ag-grid-react";
import FloatingFilter from "./floatingFilter";
export default class 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 containerStyle = {
height: 115,
width: 500
};
return (
<div style={containerStyle} className="ag-fresh">
<h1>Simple ag-Grid React Example</h1>
<AgGridReact
// properties
columnDefs={this.state.columnDefs}
rowData={this.state.rowData}
floatingFilter={true}
// events
onGridReady={this.onGridReady}>
</AgGridReact>
</div>
)
}
};

View File

@@ -0,0 +1,39 @@
import React, {Component} from "react";
import ReactDOM from "react-dom";
export default class FloatingFilter extends Component {
constructor(props) {
super();
this.state = {
parentModel: null
}
}
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;
let that = this;
let options = this.state.parentModel.map ((item, i)=>{
let that = this;
let removeMeListener = ()=>{
this.remove(item)
}
let removeMeElement = <a onClick={removeMeListener}>[x]</a>;
return <span key={i}>{item}{removeMeElement}</span>;
});
return <div>{options}</div>;
}
}

View File

@@ -0,0 +1,85 @@
import React, {Component} from "react";
import {AgGridReact} from "ag-grid-react";
import StyledRenderer from "./StyledRenderer";
export default class FloatingRowComponentExample extends Component {
constructor(props) {
super(props);
this.state = {
gridOptions: {},
rowData: this.createRowData(),
columnDefs: this.createColumnDefs(),
pinnedTopRowData: [{row: "Top Row", number: "Top Number"}],
pinnedBottomRowData: [{row: "Bottom Row", number: "Bottom Number"}]
};
this.onGridReady = this.onGridReady.bind(this);
}
onGridReady(params) {
this.gridApi = params.api;
this.columnApi = params.columnApi;
this.gridApi.sizeColumnsToFit();
}
createColumnDefs() {
return [
{
headerName: "Row",
field: "row",
width: 400,
floatingCellRendererFramework: StyledRenderer,
floatingCellRendererParams: {
style: {'fontWeight': 'bold'}
}
},
{
headerName: "Number",
field: "number",
width: 399,
floatingCellRendererFramework: StyledRenderer,
floatingCellRendererParams: {
style: {'fontStyle': 'italic'}
}
},
];
}
createRowData() {
let rowData = [];
for (let i = 0; i < 15; i++) {
rowData.push({
row: "Rou " + i,
number: Math.round(Math.random() * 100)
});
}
return rowData;
}
render() {
return (
<div style={{height: 400, width: 945}}
className="ag-fresh">
<h1>Pinned Row Renderer Example</h1>
<AgGridReact
// properties
columnDefs={this.state.columnDefs}
rowData={this.state.rowData}
pinnedTopRowData={this.state.pinnedTopRowData}
pinnedBottomRowData={this.state.pinnedBottomRowData}
// events
onGridReady={this.onGridReady}>
</AgGridReact>
</div>
);
}
};

View File

@@ -0,0 +1,13 @@
import React, {Component} from "react";
export default class CurrencyRenderer extends Component {
constructor(props) {
super(props);
}
render() {
return (
<span style={this.props.style}>{this.props.value}</span>
);
}
};

View 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>
);
}
};

View 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>
);
}
};

View File

@@ -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>
);
}
};

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

BIN
src/images/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
src/images/fire.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

BIN
src/images/flags/ar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 B

BIN
src/images/flags/br.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 B

BIN
src/images/flags/co.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 B

BIN
src/images/flags/de.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 B

BIN
src/images/flags/es.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

BIN
src/images/flags/fr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 B

BIN
src/images/flags/gb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 B

BIN
src/images/flags/gr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

BIN
src/images/flags/ie.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

BIN
src/images/flags/is.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

BIN
src/images/flags/it.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 B

BIN
src/images/flags/mt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B

BIN
src/images/flags/no.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

BIN
src/images/flags/pe.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 B

BIN
src/images/flags/pt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 B

BIN
src/images/flags/se.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 B

BIN
src/images/flags/uy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 B

Some files were not shown because too many files have changed in this diff Show More