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;