Skip to content

Instantly share code, notes, and snippets.

@clintonyeb
Created May 17, 2020 19:56
Show Gist options
  • Save clintonyeb/c9aacfbc6b38395518b4b5da595d557b to your computer and use it in GitHub Desktop.
Save clintonyeb/c9aacfbc6b38395518b4b5da595d557b to your computer and use it in GitHub Desktop.
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