Created
May 17, 2020 19:56
-
-
Save clintonyeb/c9aacfbc6b38395518b4b5da595d557b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component } from 'react'; | |
import { Line, Chart } from 'react-chartjs-2'; | |
import Select from 'react-select'; | |
import Loader from '../Loader/index.js' | |
const GREEN = '#056600'; | |
const GREEN_1 = '#067700'; | |
const GREEN_2 = '#09a900' | |
const GREEN_3 = '0eeb02' | |
const GREEN_4 = '12ff06' | |
const RED = '#4e0000'; | |
const RED_1 = '#840000' | |
const RED_2 = '#a50000' | |
const RED_3 = '#d00101' | |
const RED_4 = '#ff0606' | |
const MAX_TICK_COUNT = 15; | |
const MIN_TICK_COUNT = 6; | |
const generateLabel = (country = '') => { | |
return country.slice(0, 3) + ' / ' + country.slice(3); | |
}; | |
const data = { | |
labels: [], | |
datasets: [ | |
{ | |
fill: false, | |
backgroundColor: 'rgba(75,192,192,0.4)', | |
borderColor: 'rgba(75,192,192,1)', | |
borderCapStyle: 'butt', | |
borderDash: [], | |
borderDashOffset: 0.0, | |
borderJoinStyle: 'miter' | |
} | |
] | |
}; | |
export default class GetCurrencyData extends Component { | |
constructor(props) { | |
super(props); | |
this.state = { | |
country: props.country, | |
loading: true, //state before we start receiving date | |
data: [], //initial data array | |
tick: 6, //initial number of ticks | |
selectedOption: { label: generateLabel(props.country), value: props.country } //generates the selected option for currency | |
}; | |
//what are websockets? | |
//https://www.ably.io/concepts/websockets | |
// | |
// | |
// | |
//How to write websocket clients? | |
//https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications | |
this.ws = new WebSocket('wss://stocksimulator.coderiq.io'); | |
//open a websocket connection to websocket service | |
this.ws.onopen = (e) => { | |
console.log('websocket established for ' + props.country) | |
// send first request to websocket in json format | |
this.ws.send('{"currencyPair":"' + props.country + '"}'); | |
}; | |
//catch on message response | |
this.ws.onmessage = (e) => { | |
this.setState((state) => { | |
return { | |
loading: false, //set loading variable to false | |
data: [{ val: parseFloat(e.data).toFixed(4), timestamp: new Date() }, ...state.data].slice(0, MAX_TICK_COUNT) | |
//populating the data array | |
}; | |
}); | |
}; | |
//ChartJS service https://www.npmjs.com/package/react-chartjs-2 | |
Chart.pluginService.register({ | |
beforeRender: function(chart) { | |
if (chart.config.options.showAllTooltips) { | |
chart.pluginTooltips = []; | |
chart.config.data.datasets.forEach(function(dataset, i) { | |
chart.getDatasetMeta(i).data.forEach(function(sector, j) { | |
chart.pluginTooltips.push( | |
new Chart.Tooltip( | |
{ | |
_chart: chart.chart, | |
_chartInstance: chart, | |
_data: chart.data, | |
_options: chart.options.tooltips, | |
_active: [sector] | |
}, | |
chart | |
) | |
); | |
}); | |
}); | |
// turn off normal tooltips | |
chart.options.tooltips.enabled = false; | |
} | |
}, | |
afterDraw: function(chart, easing) { | |
if (chart.config.options.showAllTooltips) { | |
// we don't want the permanent tooltips to animate, so don't do anything till the animation runs atleast once | |
if (!chart.allTooltipsOnce) { | |
if (easing !== 1) return; | |
chart.allTooltipsOnce = true; | |
} | |
chart.options.tooltips.enabled = true; | |
Chart.helpers.each(chart.pluginTooltips, function(tooltip) { | |
tooltip.initialize(); | |
tooltip.update(); // we don't actually need this since we are not animating tooltips | |
tooltip.pivot(); | |
tooltip.transition(easing).draw(); | |
}); | |
chart.options.tooltips.enabled = false; | |
} | |
} | |
}); | |
} | |
//this method is called by reactjs on mount | |
static getDerivedStateFromProps(nextProps, prevState) { | |
if(nextProps.country !== prevState.country) { | |
debugger; | |
return { | |
country: nextProps.country, | |
loading: true, | |
data: [], | |
tick: 6, | |
selectedOption: { label: generateLabel(nextProps.country), value: nextProps.country } | |
}; | |
} | |
return null; | |
} | |
componentWillUnmount() { | |
this.ws.close(); | |
} | |
handleChange = (selectedOption) => { | |
this.setState({ selectedOption }); | |
this.props.onSubscriptionChange(selectedOption.value, this.props.keyId); | |
}; | |
render() { | |
//prepare data for select | |
const selectedOption = this.state.selectedOption; | |
const options = this.props.availableCountries.map((country) => { | |
return { | |
value: country, | |
label: generateLabel(country) | |
}; | |
}); | |
//calculate avg and percentage | |
let avg = 0, | |
percent = 0; | |
let currentValue = 0; | |
if (this.state.data != null && this.state.data.length > 0) | |
{ | |
//Extract current value from dataset | |
const currentData = this.state.data.slice(0, this.state.tick); | |
const dataset = currentData.map((x, i) => { | |
if (i === 0) currentValue = parseFloat(x.val); | |
return parseFloat(x.val); | |
}); | |
if (dataset && dataset.length) { | |
avg = dataset.reduce((a, b) => a + b, 0) / dataset.length; //calculate average for dataset | |
percent = ((avg - dataset[0]) / avg) * 100; //calculate difference between current and average of ticks | |
percent = parseFloat(percent).toFixed(2); | |
} | |
data.labels = [...Array(this.state.tick)].map((x, i) => 't' + (i + 1)); // print label for each tick | |
data.datasets[0]['data'] = dataset; | |
} | |
//algorith to get color from percent | |
function getColorForPercent(percent) | |
{ | |
var color; | |
if (percent > -1.0 && percent <= -0.8) | |
{ | |
color = RED; | |
} | |
else if (percent > -0.8 && percent <= -0.6) | |
{ | |
color = RED_1 | |
} | |
else if (percent > -0.6 && percent <= -0.4) | |
{ | |
color = RED_2 | |
} | |
else if (percent > -0.4 && percent <= -0.2) | |
{ | |
color = RED_3 | |
} | |
else if (percent > -0.2 && percent <= 0) | |
{ | |
color = RED_4 | |
} | |
else if (percent > 0 && percent <= 0.2) | |
{ | |
color = GREEN_4 | |
} | |
else if (percent > 0.2 && percent <= 0.4) | |
{ | |
color = GREEN_3 | |
} | |
else if (percent > 0.4 && percent <=0.6) | |
{ | |
color = GREEN_2 | |
} | |
else if (percent > 0.6 && percent <= 0.8) | |
{ | |
color = GREEN_1 | |
} | |
else if (percent > 0.8 && percent <= 1.0) | |
{ | |
color = GREEN | |
} | |
return color; | |
} | |
////////////////////////////////////////////////////// | |
const intValue = parseInt(currentValue).toString().length; | |
const displayCurrentValue = currentValue | |
.toFixed(4) | |
.toString() | |
.split(''); | |
if(this.state.loading) { | |
//initial screen when data has not populated yet | |
return <div className='loader'><Loader /></div> | |
} | |
//Insert JSX to display correct value of card | |
// Go through this tutorial before attempting this https://reactjs.org/docs/jsx-in-depth.html | |
return ( | |
<div style={{ backgroundColor: getColorForPercent(percent) }} className='singleCard'> | |
<div className='card-body'> | |
<div className='row'> | |
<div className='col-6'> | |
<Select className='select-country' value={selectedOption} onChange={this.handleChange} options={options} classNamePrefix='currency' /> | |
</div> | |
<div className='col-6'> | |
<div className='percentage'>{percent}%</div> | |
</div> | |
</div> | |
<div className='row'> | |
<div className='col-6'> | |
<div className='currentValue'> | |
{displayCurrentValue.map((val, index) => { | |
return ( | |
<span key={index} className={index === intValue + 1 || index === intValue + 2 ? 'small' : ''}> | |
{val} | |
</span> | |
); | |
})} | |
</div> | |
</div> | |
<div className='col-6'> | |
<div className='tick-text'>No Of Ticks</div> | |
<div className='tick-options'> | |
<div | |
onClick={() => | |
this.setState((state) => { | |
let tick = state.tick; | |
if (tick - 1 < MIN_TICK_COUNT) tick = MIN_TICK_COUNT; | |
else tick--; | |
return { | |
tick | |
}; | |
}) | |
} | |
className='minus'> | |
- | |
</div> | |
<div className='tickCount'>{this.state.tick}</div> | |
<div | |
onClick={() => | |
this.setState((state) => { | |
let tick = state.tick; | |
if (tick + 1 > MAX_TICK_COUNT) tick = MAX_TICK_COUNT; | |
else tick++; | |
return { | |
tick | |
}; | |
}) | |
} | |
className='plus'> | |
+ | |
</div> | |
</div> | |
</div> | |
</div> | |
<div style={{ height: 'calc(45vh - 120px)', width: '100%' }}> | |
<Line | |
ref={(ref) => { | |
return (this.chart = ref); | |
}} | |
id={this.props.country} | |
data={data} | |
width={100} | |
height={50} | |
options={{ | |
responsive: true, | |
maintainAspectRatio: false, | |
showAllTooltips: true, | |
tooltips: { | |
backgroundColor: 'rgba(0, 0, 0, 0)', | |
bodyFontColor: '#fff', | |
displayColors: false, | |
callbacks: { | |
label: function(tooltipItems, data) { | |
return tooltipItems.value; | |
}, | |
title: function() { | |
return ''; | |
} | |
} | |
}, | |
legend: { | |
display: false | |
}, | |
scales: { | |
xAxes: [ | |
{ | |
gridLines: { | |
display: false, | |
color: 'transparent' | |
}, | |
ticks: { | |
fontColor: '#fff' | |
} | |
} | |
], | |
yAxes: [ | |
{ | |
gridLines: { | |
display: false, | |
drawBorder: false | |
}, | |
ticks: { | |
display: false | |
} | |
} | |
] | |
} | |
}} | |
/> | |
</div> | |
</div> | |
</div> | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment