responsive chart

This commit is contained in:
Ste Vaidis 2022-12-11 17:36:04 +02:00
parent 3cb1b9a0c0
commit e33dba4584
7 changed files with 193 additions and 115 deletions

View File

@ -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 => {

View 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>;
}

View File

@ -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,17 +23,17 @@ 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),
}), }),
}), }),
}) })

View 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> { };

View File

@ -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,73 +36,110 @@ 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}
redraw={redraw}
updateMode="resize"
data={{ data={{
labels: formatChartLabels(data), labels: formatChartLabels(data),
datasets: [{ datasets: [{
@ -103,7 +148,6 @@ const CoinChart = (props: ICoinChartProps): JSX.Element => {
}], }],
}} }}
/> />
</Grid>
</div> </div>
</> </>
} }

View File

@ -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>
) )
} }

View File

@ -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;