Skip to content

Commit

Permalink
Merge pull request #35 from alecsloan/dev
Browse files Browse the repository at this point in the history
v3.5.0
  • Loading branch information
alecsloan authored May 29, 2021
2 parents 1b0e741 + 5d445ee commit c27a587
Show file tree
Hide file tree
Showing 7 changed files with 349 additions and 18 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,4 @@ npm run start

# Todo
* Add multi-language support
* Add different layout views (table, main portfolio change chart with small cards, etc.)
* Add more info about the asset on the back of the card (Circ supply, max supply, market cap, etc)
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
{
"name": "cryptolio",
"version": "3.4.2",
"version": "3.5.0",
"private": true,
"dependencies": {
"@material-ui/core": "latest",
"@material-ui/data-grid": "latest",
"@material-ui/icons": "latest",
"@material-ui/lab": "latest",
"bootstrap": "^5.0.1",
"echarts": "^5.1.0",
"echarts-for-react": "^3.0.1",
"events": "^3.0.0",
"export-from-json": "^1.3.0",
"font-awesome": "^4.7.0",
Expand Down
30 changes: 24 additions & 6 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import * as CoinGecko from './Util/CoinGecko'
import * as CoinMarketCap from './Util/CoinMarketCap'
import * as Theme from './Theme'
import AssetTable from './Components/AssetTable'
import PortfolioDonutChart from './Components/PortfolioDonutChart'
import PortfolioAreaStackChart from './Components/PortfolioAreaStackChart'

function CardRow (props) {
const cards = []
Expand Down Expand Up @@ -61,10 +63,12 @@ class App extends Component {
balanceChangeTimeframe: 'percent_change_24h',
currency: 'USD',
datasource: 'coinmarketcap',
days: 7,
decimals2: 100,
decimals3: 1,
decimals4: null,
fetchInterval: 300000,
portfolioBreakdown: "none",
renderStyle: (window.innerWidth <= 500) ? 'table' : 'card:classic',
show1hChange: true,
show24hChange: true,
Expand Down Expand Up @@ -190,7 +194,7 @@ class App extends Component {

const assets =
coinmarketcap.map(asset => ({
...coingecko.find((asset1) => (asset1.symbol === asset.symbol.toLowerCase() && asset1.name === asset.name)),
...coingecko.find((asset1) => !asset1.cgId.includes("binance-peg") && (asset1.symbol === asset.symbol.toLowerCase() && asset1.circulating_supply === asset.circulating_supply)),
...asset
}))

Expand Down Expand Up @@ -306,13 +310,27 @@ class App extends Component {

<Header addCrypto={this.addCrypto.bind(this)} assets={this.state.data.assets} availableAssets={this.state.data.availableAssets} editSetting={this.editSetting.bind(this)} refreshData={this.fetchAssetData.bind(this)} settings={this.state.settings} toggleShowSettings={this.toggleShowSettings.bind(this)} updatingData={this.state.updatingData || false} />
<hr hidden={(this.state.settings.renderStyle === 'table' && window.innerWidth <= 500)} />
<div className='content'>
{
this.state.settings.portfolioBreakdown === "stacked_line"
? <PortfolioAreaStackChart assets={this.state.data.assets} days={this.state.settings.days || 7} settings={this.state.settings}/>
: null
}
<Grid container style={{ width: '99%' }}>
{
(!this.state.settings.renderStyle || this.state.settings.renderStyle.includes('card'))
? <CardRow assets={this.state.data.assets} renderStyle={this.state.settings.renderStyle} settings={this.state.settings} setAssetPanelShown={this.setAssetPanelShown.bind(this)} />
: <AssetTable assets={this.state.data.assets} editSetting={this.editSetting.bind(this)} settings={this.state.settings} setAssetPanelShown={this.setAssetPanelShown.bind(this)} />
this.state.settings.portfolioBreakdown === "donut"
? <Grid item xs={12} md={2}>
<PortfolioDonutChart assets={this.state.data.assets} settings={this.state.settings} />
</Grid>
: null
}
</div>
<Grid item xs={12} md={this.state.settings.portfolioBreakdown === "donut" ? 10 : 12}>
{
(!this.state.settings.renderStyle || this.state.settings.renderStyle.includes('card'))
? <CardRow assets={this.state.data.assets} renderStyle={this.state.settings.renderStyle} settings={this.state.settings} setAssetPanelShown={this.setAssetPanelShown.bind(this)} />
: <AssetTable assets={this.state.data.assets} editSetting={this.editSetting.bind(this)} settings={this.state.settings} setAssetPanelShown={this.setAssetPanelShown.bind(this)} />
}
</Grid>
</Grid>
<Settings data={this.state.data} editSetting={this.editSetting.bind(this)} settings={this.state.settings} showSettings={this.state.showSettings} theme={this.state.settings.theme} toggleShowSettings={this.toggleShowSettings.bind(this)} uploadData={this.uploadData.bind(this)} />
<AssetPanel asset={this.state.assetPanelShown} editSetting={this.editSetting.bind(this)} settings={this.state.settings} removeCrypto={this.removeCrypto.bind(this)} setAssetPanelShown={this.setAssetPanelShown.bind(this)} updateExitPlan={this.updateExitPlan.bind(this)} updateHoldings={this.updateHoldings.bind(this)} updateInterest={this.updateInterest.bind(this)} />
<Hotkeys
Expand Down
203 changes: 203 additions & 0 deletions src/Components/PortfolioAreaStackChart.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import React, { Component } from 'react'
import ReactECharts from 'echarts-for-react';
import * as Util from '../Util/index'
import * as CoinMarketCap from '../Util/CoinMarketCap'
import * as CoinGecko from '../Util/CoinGecko'
import abbreviate from 'number-abbreviate'
import { Skeleton } from '@material-ui/lab'

class PortfolioAreaStackChart extends Component {
constructor (props) {
super(props);

const option = {
tooltip: {
axisPointer: {
label: {
formatter: (params) => {
return Util.getLocalizedPrice(params.value, props.settings)
}
},
type: 'cross'
},
formatter: (params) => {
let tooltip = params[0].axisValue + "<br />"

let portfolioValue = 0

params.forEach(param => {
tooltip += param.marker + param.seriesName + ": " + Util.getLocalizedPrice(Number(param.data), props.settings) + "<br />"

portfolioValue += Number(param.data)
})

tooltip += "<br /> Portfolio: " + Util.getLocalizedPrice(portfolioValue, props.settings)

return tooltip
},
showDelay: 1,
trigger: 'axis'
},
legend: {
bottom: 0,
orient: 'horizontal',
textStyle: {
color: 'white'
},
type: 'scroll'
},
xAxis: [
{
type: 'category',
boundaryGap: false
}
],
yAxis: [
{
axisLabel: {
formatter: (params) => {
return Util.getCurrencySymbol(props.settings.currency) + abbreviate(params.toFixed(2), 2, ['K', 'M'])
}
},
type: 'value'
}
]
};

if (window.innerWidth <= 959) {
option.yAxis[0].offset = -6
}

this.state = {
hasAssets: props.assets.filter(asset => asset.holdings > 0).length > 0,
option: option
}

this.getData(props.settings, props.assets);
}

async getData (settings, assets, days = 7) {
const currency = settings.currency
const assetsHeld = assets.filter(asset => asset.holdings > 0).sort((a, b) => (b.holdings * b.price) - (a.holdings * a.price))

let data

if (this.props.settings.datasource === 'coinmarketcap') {
data = await CoinMarketCap.getHistoricalAssetData(currency, assetsHeld.map(asset => asset.symbol).join(), days)
}
else {
data = await CoinGecko.getHistoricalAssetData(currency, assetsHeld.map(asset => asset.cgId), days)
}

let dates = []
let series = []

let option = this.state.option

if (data) {
assetsHeld.map((asset, index) => {
let values = []
let assetData = data

if (this.props.settings.datasource === 'coinmarketcap') {
if (assetsHeld.length > 1) {
assetData = data[asset.symbol].quotes
}
}
else {
assetData = data[index].prices
}

if (this.props.settings.datasource === 'coinmarketcap') {
for (let [key, value] of Object.entries(assetData)) {
const index = key

if (assetsHeld.length > 1) {
key = value.timestamp
value = value.quote[currency].price
} else {
value = value[currency][0]
}

let date = `${new Date(key).getMonth() + 1}/${new Date(key).getDate()}`;

if (!dates.includes(date)) {
dates.push(date);
}

if (Number(index) === assetData.length - 1) {
values.push(asset.price * asset.holdings)
} else {
values.push(value * asset.holdings);
}
}
}
else {
assetData.map((granularDataset) => {

if (index === 0) {
let date = new Date(granularDataset[0])
let min = date.getMinutes()

date.setMinutes(Math.ceil(min / 10) * 10)
date.setSeconds(0)

dates.push(date.toLocaleString());
}

return values.push(granularDataset[1] * asset.holdings)
})
}

return series.push({
name: `${asset.name} (${asset.symbol})`,
type: 'line',
stack: 'portfolio',
areaStyle: {},
emphasis: {
focus: 'series'
},
data: values
});
})
}

option.xAxis[0].data = dates
option.series = series

this.setState({
option: option
})
this.echartsInstance.setOption(option, true)
}

componentDidMount () {
this.echartsInstance = this.echartsReactRef.getEchartsInstance();
}

UNSAFE_componentWillReceiveProps (nextProps, nextContext) {
const assetsHeld = nextProps.assets.filter(asset => asset.holdings > 0)

if (nextProps.days || assetsHeld.length === 0) {
this.getData(nextProps.settings, nextProps.assets, nextProps.days);
}
}

render() {
return (
<div>
<ReactECharts
className={!this.state.option.series ? "d-none" : null}
option={this.state.option}
ref={(e) => {
this.echartsReactRef = e;
}}
style={{ height: '100%', minHeight: '300px' }}
/>
<Skeleton animation="wave" className="m-auto" hidden={this.state.option.series} height={300} width={'90%'} />
</div>
)
}
}

export default PortfolioAreaStackChart
58 changes: 58 additions & 0 deletions src/Components/PortfolioDonutChart.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from 'react'
import ReactECharts from 'echarts-for-react';
import * as Util from '../Util/index'

function PortfolioDonutChart (props) {
const option = {
tooltip: {
formatter: (params) => {
return (
params.name + ": " + Util.getLocalizedPrice(params.data.holdings * params.data.price, props.settings) +
"<br />Holdings: " + Util.getLocalizedNumber(Number(params.data.holdings), props.settings)
)
},
position: 'right',
trigger: 'item'
},
legend: {
orient: 'vertical',
top: '175px',
textStyle: {
color: 'white'
}
},
series: [
{
avoidLabelOverlap: true,
center: ['50%', '75px'],
data: props.assets.sort((a, b) => ((b.holdings || 0.000001) * b.price) - ((a.holdings || 0.000001) * a.price)).map(asset => ({holdings: asset.holdings, name: `${asset.name} (${asset.symbol})`, price: asset.price, value: (asset.holdings * asset.price).toFixed(2)})),
itemStyle: {
borderRadius: 5,
borderWidth: 5
},
label: {
show: false,
position: 'center'
},
name: 'Asset',
radius: ['40%', '70%'],
type: 'pie'
}
]
};

if (window.innerWidth <= 959) {
option.legend.top = '80%';
option.legend.type = 'scroll';
option.tooltip.position = 'bottom';
option.series[0].center = ['50%', '35%'];
}

return (
props.settings.portfolioBreakdown === "donut"
? <ReactECharts option={option} style={{ height: '100%', minHeight: '300px' }} />
: null
)
}

export default PortfolioDonutChart
Loading

0 comments on commit c27a587

Please sign in to comment.