responsive chart
This commit is contained in:
parent
3cb1b9a0c0
commit
e33dba4584
@ -3,11 +3,6 @@ var router = express.Router();
|
|||||||
const api_helper = require('./thirdparty_api')
|
const api_helper = require('./thirdparty_api')
|
||||||
const config = require('./config');
|
const config = require('./config');
|
||||||
|
|
||||||
// total coins 13081
|
|
||||||
// per page coins 100
|
|
||||||
// max page 131
|
|
||||||
// last lenth 81
|
|
||||||
|
|
||||||
router.get('/coins/markets', function (req, res) {
|
router.get('/coins/markets', function (req, res) {
|
||||||
let page = req.query.page || 1;
|
let page = req.query.page || 1;
|
||||||
let per_page = req.query.per_page || 100;
|
let per_page = req.query.per_page || 100;
|
||||||
@ -15,9 +10,6 @@ router.get('/coins/markets', function (req, res) {
|
|||||||
|
|
||||||
api_helper.REMOTE_API_call(url)
|
api_helper.REMOTE_API_call(url)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
console.log("response.length : ", response.length);
|
|
||||||
console.log("response.page : ", page);
|
|
||||||
//console.log("url : ", url);
|
|
||||||
res.json(response);
|
res.json(response);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
@ -29,7 +21,6 @@ router.get('/coins/markets', function (req, res) {
|
|||||||
|
|
||||||
router.get('/count', function (req, res) {
|
router.get('/count', function (req, res) {
|
||||||
let url = config.coingecko.api_url + '/global';
|
let url = config.coingecko.api_url + '/global';
|
||||||
console.log("url: ", url);
|
|
||||||
|
|
||||||
api_helper.REMOTE_API_call(url)
|
api_helper.REMOTE_API_call(url)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
@ -45,7 +36,6 @@ router.get('/count', function (req, res) {
|
|||||||
router.get('/coin/:id', function (req, res) {
|
router.get('/coin/:id', function (req, res) {
|
||||||
let id = req.params['id'];
|
let id = req.params['id'];
|
||||||
let url = config.coingecko.api_url + '/coins/' + id;
|
let url = config.coingecko.api_url + '/coins/' + id;
|
||||||
console.log("id: ", id);
|
|
||||||
|
|
||||||
api_helper.REMOTE_API_call(url)
|
api_helper.REMOTE_API_call(url)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
@ -57,9 +47,11 @@ router.get('/coin/:id', function (req, res) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get('/coin/:id/chart', function (req, res) {
|
router.get('/coin/:id/chart/:days?', function (req, res) {
|
||||||
let id = req.params['id'];
|
let id = req.params['id'];
|
||||||
let url = config.coingecko.api_url + '/coins/' + id + '/market_chart?vs_currency=usd&days=max';
|
let days = req.params['days'] || 'max';
|
||||||
|
let url = config.coingecko.api_url + '/coins/' + id + '/market_chart?vs_currency=usd&days=' + days;
|
||||||
|
|
||||||
console.log("chart url:", url)
|
console.log("chart url:", url)
|
||||||
api_helper.REMOTE_API_call(url)
|
api_helper.REMOTE_API_call(url)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
|||||||
45
Front/src/coinApi-types.ts
Normal file
45
Front/src/coinApi-types.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
export interface IGetGlobalResponse {
|
||||||
|
active_cryptocurrencies: number;
|
||||||
|
markets: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGetCoinsListRequest {
|
||||||
|
page: number;
|
||||||
|
per_page: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGetCoinsListResponse {
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGetCoinsCountResponse {
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGetCoinInfoResponse {
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGetCoinInfoRequest {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// export interface IGetCoinChartRequest {
|
||||||
|
// page: number;
|
||||||
|
// per_page: number;
|
||||||
|
// }
|
||||||
|
|
||||||
|
export interface IChartDataItem {
|
||||||
|
[index: number]: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGetCoinChartRequest {
|
||||||
|
coin: string;
|
||||||
|
days: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGetCoinChartResponse {
|
||||||
|
market_caps: Array<IChartDataItem>;
|
||||||
|
prices: Array<IChartDataItem>;
|
||||||
|
total_volumes: Array<IChartDataItem>;
|
||||||
|
}
|
||||||
@ -1,48 +1,21 @@
|
|||||||
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
|
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
|
||||||
|
import {
|
||||||
|
IGetGlobalResponse,
|
||||||
|
IGetCoinsListRequest,
|
||||||
|
IGetCoinsListResponse,
|
||||||
|
IGetCoinsCountResponse,
|
||||||
|
IGetCoinInfoRequest,
|
||||||
|
IGetCoinInfoResponse,
|
||||||
|
IGetCoinChartRequest,
|
||||||
|
IGetCoinChartResponse,
|
||||||
|
} from './coinApi-types'
|
||||||
|
|
||||||
// URL's
|
|
||||||
const baseUrl = "http://127.0.0.1:8080";
|
const baseUrl = "http://127.0.0.1:8080";
|
||||||
const marketsUrl = '/coins/markets';
|
const marketsUrl = '/coins/markets';
|
||||||
const countUrl = '/count';
|
const countUrl = '/count';
|
||||||
const globalUrl = '/global';
|
const globalUrl = '/global';
|
||||||
const coinInfoUrl = (id:string) => `/coin/${id}`;
|
const coinInfoUrl = (id: string) => `/coin/${id}`;
|
||||||
const coinChartUrl = (id:string) => `/coin/${id}/chart`;
|
const coinChartUrl = (coin: string, days: string) => `/coin/${coin}/chart/${days}`;
|
||||||
|
|
||||||
export interface IGetGlobalResponse {
|
|
||||||
active_cryptocurrencies: number;
|
|
||||||
markets: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IGetCoinsListResponse {
|
|
||||||
[key: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IGetCoinsCountResponse {
|
|
||||||
count: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IGetCoinsInfoResponse {
|
|
||||||
[key: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IGetCoinsInfoRequest {
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IGetCoinsChartRequest {
|
|
||||||
page: number;
|
|
||||||
per_page: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IChartDataItem {
|
|
||||||
[index: number]: number,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IGetCoinsChartResponse {
|
|
||||||
market_caps: Array<IChartDataItem>;
|
|
||||||
prices: Array<IChartDataItem>;
|
|
||||||
total_volumes: Array<IChartDataItem>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const coinListApi = createApi({
|
export const coinListApi = createApi({
|
||||||
baseQuery: fetchBaseQuery({ baseUrl: baseUrl }),
|
baseQuery: fetchBaseQuery({ baseUrl: baseUrl }),
|
||||||
@ -50,18 +23,18 @@ export const coinListApi = createApi({
|
|||||||
getGlobal: builder.query<IGetGlobalResponse, void>({
|
getGlobal: builder.query<IGetGlobalResponse, void>({
|
||||||
query: () => globalUrl,
|
query: () => globalUrl,
|
||||||
}),
|
}),
|
||||||
getCoinsList: builder.query<IGetCoinsListResponse, IGetCoinsChartRequest>({
|
getCoinsList: builder.query<IGetCoinsListResponse, IGetCoinsListRequest>({
|
||||||
query: (payload: IGetCoinsChartRequest) => `${marketsUrl}?per_page=${payload.per_page}&page=${payload.page}`,
|
query: (payload: IGetCoinsListRequest) => `${marketsUrl}?per_page=${payload.per_page}&page=${payload.page}`,
|
||||||
}),
|
}),
|
||||||
getCoinsCount: builder.query<IGetCoinsCountResponse, void>({
|
getCoinsCount: builder.query<IGetCoinsCountResponse, void>({
|
||||||
query: () => countUrl,
|
query: () => countUrl,
|
||||||
}),
|
}),
|
||||||
getCoinInfo: builder.query<IGetCoinsInfoResponse, string>({
|
getCoinInfo: builder.query<IGetCoinInfoResponse, string>({
|
||||||
query: (id: string) => coinInfoUrl(id),
|
query: (id: string) => coinInfoUrl(id),
|
||||||
}),
|
}),
|
||||||
getCoinChart: builder.query<IGetCoinsChartResponse, string>({
|
getCoinChart: builder.query<IGetCoinChartResponse, IGetCoinChartRequest>({
|
||||||
query: (id: string) => coinChartUrl(id),
|
query: (payload: {coin: string, days: string}) => coinChartUrl(payload.coin, payload.days),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -71,4 +44,4 @@ export const {
|
|||||||
useGetCoinsCountQuery,
|
useGetCoinsCountQuery,
|
||||||
useGetCoinInfoQuery,
|
useGetCoinInfoQuery,
|
||||||
useGetCoinChartQuery
|
useGetCoinChartQuery
|
||||||
} = coinListApi;
|
} = coinListApi;
|
||||||
|
|||||||
20
Front/src/coinDetails/coinChart-types.ts
Normal file
20
Front/src/coinDetails/coinChart-types.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
export interface ICoinChartProps {
|
||||||
|
coin: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IChartDataItem {
|
||||||
|
[index: number]: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IChartData {
|
||||||
|
market_caps: Array<IChartDataItem>;
|
||||||
|
prices: Array<IChartDataItem>;
|
||||||
|
total_volumes: Array<IChartDataItem>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IChartDataFormattedItem {
|
||||||
|
x: Date;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IChartDataFormatted extends Array<IChartDataFormattedItem> { };
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
Chart as ChartJS,
|
Chart as ChartJS,
|
||||||
CategoryScale,
|
CategoryScale,
|
||||||
@ -17,6 +18,13 @@ import Button from '@mui/material/Button';
|
|||||||
import ButtonGroup from '@mui/material/ButtonGroup';
|
import ButtonGroup from '@mui/material/ButtonGroup';
|
||||||
import Grid from '@mui/material/Grid';
|
import Grid from '@mui/material/Grid';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ICoinChartProps,
|
||||||
|
IChartDataItem,
|
||||||
|
IChartData,
|
||||||
|
IChartDataFormatted
|
||||||
|
} from './coinChart-types';
|
||||||
|
|
||||||
ChartJS.register(
|
ChartJS.register(
|
||||||
CategoryScale,
|
CategoryScale,
|
||||||
LinearScale,
|
LinearScale,
|
||||||
@ -28,82 +36,118 @@ ChartJS.register(
|
|||||||
Legend
|
Legend
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export interface IDurationOption {
|
||||||
export interface ICoinChartProps {
|
label: string;
|
||||||
coin: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IChartDataItem {
|
export interface IDurationOptions extends Array<IDurationOption> { }
|
||||||
[index: number]: number,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IChartData {
|
|
||||||
market_caps: Array<IChartDataItem>;
|
|
||||||
prices: Array<IChartDataItem>;
|
|
||||||
total_volumes: Array<IChartDataItem>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IChartDataFormattedItem {
|
|
||||||
x: Date;
|
|
||||||
y: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IChartDataFormatted extends Array<IChartDataFormattedItem> { };
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const options = {
|
|
||||||
responsive: false,
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
plugins: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
const CoinChart = (props: ICoinChartProps): JSX.Element => {
|
const CoinChart = (props: ICoinChartProps): JSX.Element => {
|
||||||
// console.log("props: ", props);
|
|
||||||
const { data, isSuccess: isSuccessChart } = useGetCoinChartQuery(props.coin);
|
const ref = React.useRef<any>(null);
|
||||||
|
const [duration, setDuration] = React.useState('max')
|
||||||
|
const [redraw, setRedraw] = React.useState(false);
|
||||||
|
|
||||||
|
// BUG: responsive and redraw toghether cause double render
|
||||||
|
React.useEffect(() => {
|
||||||
|
function handleResize() {
|
||||||
|
setTimeout(function () { setRedraw(true) }, 1000);
|
||||||
|
setTimeout(function () { setRedraw(false) }, 2000);
|
||||||
|
}
|
||||||
|
window.addEventListener('resize', handleResize)
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
const chartRequest = {
|
||||||
|
coin: props.coin,
|
||||||
|
days: duration
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data, isSuccess, refetch } = useGetCoinChartQuery(chartRequest);
|
||||||
|
|
||||||
|
|
||||||
|
const durationOptions: IDurationOptions = [
|
||||||
|
{ label: "1D", value: '1' },
|
||||||
|
{ label: "2W", value: '14' },
|
||||||
|
{ label: "1M", value: '30' },
|
||||||
|
{ label: "3M", value: '90' },
|
||||||
|
{ label: "1Y", value: '365' },
|
||||||
|
{ label: "MAX", value: 'max' }
|
||||||
|
];
|
||||||
|
|
||||||
function formatChartData(data: IChartData): IChartDataFormatted {
|
function formatChartData(data: IChartData): IChartDataFormatted {
|
||||||
const chartData = data['prices'].map(function (value: IChartDataItem) {
|
return data['prices'].map(function (value: IChartDataItem) {
|
||||||
return {
|
return {
|
||||||
x: new Date(value[0]),
|
x: new Date(value[0]),
|
||||||
y: value[1],
|
y: value[1],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return chartData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatChartLabels(data: any): any {
|
function formatChartLabels(data: any): any {
|
||||||
const labels = data['prices'].map((value: (string | number | Date)[]) => format(new Date(value[0]), 'MM/dd/yyyy'));
|
return data['prices'].map((value: (string | number | Date)[]) =>
|
||||||
return labels;
|
format(new Date(value[0]), 'MM/dd/yyyy')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function durationHandler(value: string): void {
|
||||||
|
setDuration(value)
|
||||||
|
refetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
const chartOptions = {
|
||||||
|
elements: {
|
||||||
|
point: {
|
||||||
|
radius: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
color: '#444444',
|
||||||
|
borderColor: '#444444',
|
||||||
|
backgroundColor: '#444444',
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: true,
|
||||||
|
aspectRatio: 2,
|
||||||
|
pointRadius: 0,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="chart-container" style={{ margin: 5, padding: 5 }}>
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className="chart"
|
||||||
|
>
|
||||||
{
|
{
|
||||||
data && <>
|
data && <>
|
||||||
<Grid container justifyContent="flex-start" style={{ marginTop: 0 }}>
|
<Grid container justifyContent="flex-start" style={{ marginTop: 0 }}>
|
||||||
<ButtonGroup variant="contained" aria-label="outlined primary button group">
|
<ButtonGroup variant="contained" aria-label="outlined primary button group">
|
||||||
<Button onClick={() => { }}>1D</Button>
|
{
|
||||||
<Button onClick={() => { }}>14D</Button>
|
durationOptions.map(item => <Button
|
||||||
<Button onClick={() => { }}>1M</Button>
|
key={item.value}
|
||||||
<Button onClick={() => { }}>3M</Button>
|
variant={item.value === duration ? 'outlined' : 'contained'}
|
||||||
<Button onClick={() => { }}>1Y</Button>
|
onClick={() => { durationHandler(item.value) }}>{item.label}
|
||||||
<Button onClick={() => { }}>MAX</Button>
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</Grid>
|
</Grid>
|
||||||
<div className="chart-container" style={{ margin: 5, padding: 5 }}>
|
<div id="chart" className="chart-container" style={{ position: "relative", height: "auto", width: "80vw" }}>
|
||||||
<Grid container justifyContent="flex-end" style={{ marginTop: 0 }}>
|
<Line
|
||||||
<Line
|
options={chartOptions}
|
||||||
data={{
|
redraw={redraw}
|
||||||
labels: formatChartLabels(data),
|
updateMode="resize"
|
||||||
datasets: [{
|
data={{
|
||||||
label: 'Dataset',
|
labels: formatChartLabels(data),
|
||||||
data: formatChartData(data)
|
datasets: [{
|
||||||
}],
|
label: 'Dataset',
|
||||||
}}
|
data: formatChartData(data)
|
||||||
/>
|
}],
|
||||||
</Grid>
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,25 +40,23 @@ const CoinInfo = (props: ICoinInfoProps): JSX.Element => {
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid container justifyContent="flex-start" style={{ marginTop: 30 }}>
|
{/* <Grid container justifyContent="flex-start" style={{ marginTop: 30 }}>
|
||||||
{data.links.subreddit_url &&
|
{data.links.subreddit_url &&
|
||||||
<Button style={{ marginRight: 10 }} variant="outlined" startIcon={<RedditIcon />} href={data.links.subreddit_url}>
|
<Button style={{ marginRight: 10 }} variant="outlined" startIcon={<RedditIcon />} href={data.links.subreddit_url}>
|
||||||
Reddit
|
Reddit
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
|
||||||
{data.links.twitter_screen_name &&
|
{data.links.twitter_screen_name &&
|
||||||
<Button style={{ marginRight: 10 }} variant="outlined" startIcon={<TwitterIcon />} href={`https://twitter.com/${data.links.twitter_screen_name}`}>
|
<Button style={{ marginRight: 10 }} variant="outlined" startIcon={<TwitterIcon />} href={`https://twitter.com/${data.links.twitter_screen_name}`}>
|
||||||
Twitter
|
Twitter
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
|
||||||
{data.links.chat_url &&
|
{data.links.chat_url &&
|
||||||
<Button style={{ marginRight: 10 }} variant="outlined" startIcon={<LinkIcon />} href={data.links.chat_url}>
|
<Button style={{ marginRight: 10 }} variant="outlined" startIcon={<LinkIcon />} href={data.links.chat_url}>
|
||||||
Discord
|
Discord
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
</Grid>
|
</Grid> */}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,6 +32,12 @@ header a {
|
|||||||
color: #e15241
|
color: #e15241
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* #chart {
|
||||||
|
position: absolute;
|
||||||
|
height: 400px;
|
||||||
|
} */
|
||||||
|
|
||||||
.coinName {
|
.coinName {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user