Skip to content

Commit 3d5ce0e

Browse files
authored
Merge pull request #1707 from kleros/feat(web)/extra-chart-staked-pnk-per-court
feat: add staked pnk per court chart, slightly modify skeleton size, …
2 parents 1f37740 + c0b259e commit 3d5ce0e

File tree

5 files changed

+167
-93
lines changed

5 files changed

+167
-93
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import React, { useCallback } from "react";
2+
import styled, { useTheme } from "styled-components";
3+
import {
4+
Chart as ChartJS,
5+
BarElement,
6+
Tooltip,
7+
CategoryScale,
8+
LinearScale,
9+
PointElement,
10+
LineElement,
11+
TimeScale,
12+
ChartOptions,
13+
} from "chart.js";
14+
import ChartDataLabels from "chartjs-plugin-datalabels";
15+
import { Bar } from "react-chartjs-2";
16+
import "chartjs-adapter-moment";
17+
18+
ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, TimeScale, Tooltip);
19+
const formatter = new Intl.NumberFormat("en", { notation: "compact" });
20+
21+
const BarContainer = styled.div`
22+
height: 220px;
23+
margin-top: 16px;
24+
`;
25+
26+
ChartJS.register(BarElement);
27+
28+
export interface IBarChartData {
29+
labels: string[];
30+
data: number[];
31+
total: number;
32+
}
33+
34+
interface IBarChartProps {
35+
chartData: IBarChartData;
36+
}
37+
38+
const BarChart: React.FC<IBarChartProps> = ({ chartData }) => {
39+
const theme = useTheme();
40+
const getPercentValue = useCallback(
41+
(value: number) => `${Math.floor((value * 100) / chartData.total)} %`,
42+
[chartData]
43+
);
44+
45+
const formatPNKValue = useCallback((value: number) => formatter.format(value), []);
46+
47+
const tickSize = 5; // suggested, if that many labels can't fit, chart will use even labels
48+
49+
const options: ChartOptions<"bar"> = {
50+
responsive: true,
51+
maintainAspectRatio: false,
52+
scales: {
53+
x: {
54+
grid: { display: false },
55+
ticks: {
56+
color: theme.secondaryText,
57+
},
58+
},
59+
y: {
60+
grid: { color: theme.stroke, borderDash: [4, 4] },
61+
ticks: {
62+
color: theme.secondaryText,
63+
stepSize: (chartData.total * tickSize) / 100,
64+
callback: (value) => getPercentValue(value),
65+
},
66+
max: chartData.total,
67+
},
68+
},
69+
plugins: {
70+
datalabels: {
71+
anchor: "end",
72+
align: "top",
73+
offset: -4,
74+
color: theme.primaryText,
75+
font: {
76+
weight: "bold",
77+
},
78+
formatter: formatPNKValue,
79+
},
80+
tooltip: {
81+
backgroundColor: theme.whiteBackground,
82+
titleColor: theme.primaryText,
83+
borderColor: theme.stroke,
84+
borderWidth: 1,
85+
displayColors: false,
86+
callbacks: {
87+
label: (context) => getPercentValue(context.parsed.y),
88+
labelTextColor: () => theme.primaryText,
89+
},
90+
},
91+
},
92+
};
93+
94+
return (
95+
<BarContainer>
96+
<Bar
97+
data={{
98+
labels: chartData.labels,
99+
datasets: [
100+
{
101+
data: chartData.data,
102+
backgroundColor: theme.secondaryPurple,
103+
hoverBackgroundColor: theme.primaryBlue,
104+
maxBarThickness: 60,
105+
},
106+
],
107+
}}
108+
options={options}
109+
plugins={[ChartDataLabels]}
110+
/>
111+
</BarContainer>
112+
);
113+
};
114+
115+
export default BarChart;
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,5 @@
1-
import React, { useCallback } from "react";
2-
import styled, { useTheme } from "styled-components";
3-
4-
import { Chart as ChartJS, BarElement } from "chart.js";
5-
import ChartDataLabels from "chartjs-plugin-datalabels";
6-
import { Bar } from "react-chartjs-2";
7-
import "chartjs-adapter-moment";
8-
9-
const BarContainer = styled.div`
10-
height: 220px;
11-
margin-top: 16px;
12-
`;
13-
14-
ChartJS.register(BarElement);
1+
import React from "react";
2+
import BarChart, { IBarChartData } from "./BarChart";
153

164
export type CasesByCourtsChartData = { labels: string[]; cases: number[]; totalCases: number };
175

@@ -20,82 +8,13 @@ interface ICasesByCourtsChart {
208
}
219

2210
const CasesByCourtsChart: React.FC<ICasesByCourtsChart> = ({ data }) => {
23-
const theme = useTheme();
24-
const getPercentValue = useCallback((value: number) => `${Math.floor((value * 100) / data.totalCases)} %`, [data]);
25-
const tickSize = 5; // this is suggested, if that many labels can't fit, chart will use even labels
26-
27-
const options = {
28-
responsive: true,
29-
maintainAspectRatio: false,
30-
tooltips: {
31-
position: "nearest",
32-
},
33-
scales: {
34-
x: {
35-
grid: { display: false },
36-
ticks: {
37-
color: theme.secondaryText,
38-
},
39-
},
40-
y: {
41-
grid: { color: theme.stroke, borderDash: [4, 4] },
42-
ticks: {
43-
color: theme.secondaryText,
44-
stepSize: (data.totalCases * tickSize) / 100,
45-
callback: (value) => getPercentValue(value),
46-
},
47-
max: data.totalCases,
48-
},
49-
},
50-
plugins: {
51-
datalabels: {
52-
anchor: "end",
53-
align: "top",
54-
offset: -4,
55-
color: theme.primaryText,
56-
font: {
57-
weight: "bold",
58-
},
59-
},
60-
tooltip: {
61-
backgroundColor: theme.whiteBackground,
62-
titleColor: theme.primaryText,
63-
borderColor: theme.stroke,
64-
borderWidth: 1,
65-
displayColors: false,
66-
callbacks: {
67-
label: (context) => getPercentValue(context.parsed.y),
68-
labelTextColor: () => theme.primaryText,
69-
},
70-
},
71-
},
11+
const chartData: IBarChartData = {
12+
labels: data.labels,
13+
data: data.cases,
14+
total: data.totalCases,
7215
};
7316

74-
return (
75-
<BarContainer>
76-
{
77-
// eslint-disable-next-line
78-
// @ts-ignore
79-
<Bar
80-
{...{
81-
data: {
82-
labels: data.labels,
83-
datasets: [
84-
{
85-
data: data.cases,
86-
backgroundColor: theme.secondaryPurple,
87-
hoverBackgroundColor: theme.primaryBlue,
88-
maxBarThickness: 60,
89-
},
90-
],
91-
},
92-
options,
93-
}}
94-
plugins={[ChartDataLabels]}
95-
/>
96-
}
97-
</BarContainer>
98-
);
17+
return <BarChart chartData={chartData} />;
9918
};
10019

10120
export default CasesByCourtsChart;

web/src/pages/Home/CourtOverview/Chart.tsx

+22-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { responsiveSize } from "styles/responsiveSize";
1212

1313
import { StyledSkeleton } from "components/StyledSkeleton";
1414

15+
import StakedPNKByCourtsChart, { StakedPNKByCourtsChartData } from "./StakedPNKByCourtsChart";
1516
import CasesByCourtsChart, { CasesByCourtsChartData } from "./CasesByCourtsChart";
1617
import TimeSeriesChart from "./TimeSeriesChart";
1718

@@ -28,6 +29,7 @@ const StyledDropdown = styled(DropdownSelect)`
2829

2930
const CHART_OPTIONS = [
3031
{ text: "Staked PNK", value: "stakedPNK" },
32+
{ text: "Staked PNK per court", value: "stakedPNKPerCourt" },
3133
{ text: "Cases", value: "cases" },
3234
{ text: "Cases per court", value: "casesPerCourt" },
3335
];
@@ -81,18 +83,35 @@ const Chart: React.FC = () => {
8183
{ labels: [], cases: [], totalCases: 0 }
8284
);
8385

86+
const processedStakedPNKData = courtsChartData?.reduce(
87+
(accData: StakedPNKByCourtsChartData, current) => {
88+
return {
89+
labels: [...accData.labels, current.name ?? ""],
90+
stakes: [...accData.stakes, parseFloat(formatUnits(current.stake, 18))],
91+
totalStake: accData.totalStake + parseFloat(formatUnits(current.stake, 18)),
92+
};
93+
},
94+
{ labels: [], stakes: [], totalStake: 0 }
95+
);
96+
8497
const ChartComponent = useMemo(() => {
8598
switch (chartOption) {
8699
case "casesPerCourt":
87100
return processedCourtsData ? (
88101
<CasesByCourtsChart data={processedCourtsData} />
89102
) : (
90-
<StyledSkeleton height={233} />
103+
<StyledSkeleton height={234} />
104+
);
105+
case "stakedPNKPerCourt":
106+
return processedStakedPNKData ? (
107+
<StakedPNKByCourtsChart data={processedStakedPNKData} />
108+
) : (
109+
<StyledSkeleton height={234} />
91110
);
92111
default:
93-
return processedData ? <TimeSeriesChart data={processedData} /> : <StyledSkeleton height={233} />;
112+
return processedData ? <TimeSeriesChart data={processedData} /> : <StyledSkeleton height={234} />;
94113
}
95-
}, [processedCourtsData, processedData, chartOption]);
114+
}, [processedCourtsData, processedStakedPNKData, processedData, chartOption]);
96115

97116
return (
98117
<Container>

web/src/pages/Home/CourtOverview/Header.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
import React from "react";
22
import styled from "styled-components";
33

4+
import { responsiveSize } from "styles/responsiveSize";
5+
46
import { useNavigate } from "react-router-dom";
57

68
import { Button } from "@kleros/ui-components-library";
79

810
import Bookmark from "svgs/icons/bookmark.svg";
911

10-
import { responsiveSize } from "styles/responsiveSize";
11-
1212
const StyledHeader = styled.div`
1313
display: flex;
1414
flex-wrap: wrap;
1515
justify-content: space-between;
1616
gap: 0 12px;
17+
margin-bottom: ${responsiveSize(16, 0)};
1718
`;
1819

1920
const StyledH1 = styled.h1`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import React from "react";
2+
import BarChart, { IBarChartData } from "./BarChart";
3+
4+
export type StakedPNKByCourtsChartData = { labels: string[]; stakes: number[]; totalStake: number };
5+
6+
interface IStakedPNKByCourtsChart {
7+
data: StakedPNKByCourtsChartData;
8+
}
9+
10+
const StakedPNKByCourtsChart: React.FC<IStakedPNKByCourtsChart> = ({ data }) => {
11+
const chartData: IBarChartData = {
12+
labels: data.labels,
13+
data: data.stakes,
14+
total: data.totalStake,
15+
};
16+
17+
return <BarChart chartData={chartData} />;
18+
};
19+
20+
export default StakedPNKByCourtsChart;

0 commit comments

Comments
 (0)