diff --git a/App.tsx b/App.tsx new file mode 100644 index 00000000000..3f306c38b87 --- /dev/null +++ b/App.tsx @@ -0,0 +1,90 @@ +import React, { Component } from 'react'; +import DataStreamer, { ServerRespond } from './DataStreamer'; +import Graph from './Graph'; +import './App.css'; + +/** + * State declaration for + */ +interface IState { + data: ServerRespond[], + showGraph: boolean, +} + +/** + * The parent element of the react app. + * It renders title, button and Graph react element. + */ +class App extends Component<{}, IState> { + constructor(props: {}) { + super(props); + + this.state = { + // data saves the server responds. + // We use this state to parse data down to the child element (Graph) as element property + data: [], + showGraph: false, + }; + } + + /** + * Render Graph react component with state.data parse as property data + */ + renderGraph() { + if (this.state.showGraph){ + return () + } + } + + /** + * Get new data from server and update the state with the new data + */ + getDataFromServer() { + let x = 0; + const interval = setInterval(()=>{ + DataStreamer.getData((serverResponds: ServerRespond[]) => { + // Update the state by creating a new array of data that consists of + // Previous data in the state and the new data from server + this.setState({ + data: serverResponds, + showGraph : true, + }); + }); + x++; + if(x > 1000){ + clearInterval(interval); + + } + + }, 100); + } + + /** + * Render the App react component + */ + render() { + return ( +
+
+ Bank & Merge Co Task 2 +
+
+ +
+ {this.renderGraph()} +
+
+
+ ) + } +} + +export default App; diff --git a/Graph.tsx b/Graph.tsx new file mode 100644 index 00000000000..4e7a9e686ad --- /dev/null +++ b/Graph.tsx @@ -0,0 +1,80 @@ +import React, { Component } from 'react'; +import { Table } from '@finos/perspective'; +import { ServerRespond } from './DataStreamer'; +import './Graph.css'; + +/** + * Props declaration for + */ +interface IProps { + data: ServerRespond[], +} + +/** + * Perspective library adds load to HTMLElement prototype. + * This interface acts as a wrapper for Typescript compiler. + */ +interface PerspectiveViewerElement extends HTMLElement { + load: (table: Table) => void, +} + +/** + * React component that renders Perspective based on data + * parsed from its parent through data property. + */ +class Graph extends Component { + // Perspective table + table: Table | undefined; + + render() { + return React.createElement('perspective-viewer'); + } + + componentDidMount() { + // Get element to attach the table from the DOM. + const elem = document.getElementsByTagName('perspective-viewer')[0] as unknown as PerspectiveViewerElement; + + const schema = { + stock: 'string', + top_ask_price: 'float', + top_bid_price: 'float', + timestamp: 'date', + }; + + if (window.perspective && window.perspective.worker()){ + //if (window.perspective) { + this.table = window.perspective.worker().table(schema); + } + if (this.table) { + // Load the `table` in the `` DOM reference. + + // Add more Perspective configurations here. + elem.load(this.table); + elem.setAttribute('view', 'y_line'); + elem.setAttribute('column_pivots', '["stock"]'); + elem.setAttribute('row_pivots', '["timestamp"]'); + elem.setAttribute('columns', '["top_ask_price"]'); + elem.setAttribute('aggregates', '{"stock":"distinct count", "top_ask_price":"avg" , "top_bid_price":"avg" , "timestamp":"distinct count"}'); + + } + } + + componentDidUpdate() { + // Everytime the data props is updated, insert the data into Perspective table + if (this.table) { + // As part of the task, you need to fix the way we update the data props to + // avoid inserting duplicated entries into Perspective table again. + this.table.update(this.props.data.map((el: any) => { + // Format the data from ServerRespond to the schema + return { + stock: el.stock, + top_ask_price: el.top_ask && el.top_ask.price || 0, + top_bid_price: el.top_bid && el.top_bid.price || 0, + timestamp: el.timestamp, + }; + })); + } + } +} + +export default Graph;