Skip to content

Commit a1dff3b

Browse files
authored
Merge pull request #1650 from kleros/feat/cases-by-court-chart
Feat/cases by court chart
2 parents 4c82235 + a7f7b65 commit a1dff3b

File tree

6 files changed

+158
-3
lines changed

6 files changed

+158
-3
lines changed

web/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
"amqplib": "^0.10.3",
9494
"chart.js": "^3.9.1",
9595
"chartjs-adapter-moment": "^1.0.1",
96+
"chartjs-plugin-datalabels": "^2.2.0",
9697
"core-js": "^3.35.0",
9798
"ethers": "^5.7.2",
9899
"graphql": "^16.8.1",

web/src/hooks/queries/useHomePageQuery.ts

+4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ const homePageQuery = graphql(`
1919
activeJurors
2020
cases
2121
}
22+
courts {
23+
name
24+
numberDisputes
25+
}
2226
}
2327
`);
2428

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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);
15+
16+
export type CasesByCourtsChartData = { labels: string[]; cases: number[]; totalCases: number };
17+
18+
interface ICasesByCourtsChart {
19+
data: CasesByCourtsChartData;
20+
}
21+
22+
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+
},
72+
};
73+
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+
);
99+
};
100+
101+
export default CasesByCourtsChart;

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

+30-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState } from "react";
1+
import React, { useMemo, useState } from "react";
22
import styled from "styled-components";
33

44
import { formatUnits } from "viem";
@@ -11,6 +11,7 @@ import { responsiveSize } from "styles/responsiveSize";
1111

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

14+
import CasesByCourtsChart, { CasesByCourtsChartData } from "./CasesByCourtsChart";
1415
import TimeSeriesChart from "./TimeSeriesChart";
1516

1617
const Container = styled.div`
@@ -27,7 +28,7 @@ const StyledDropdown = styled(DropdownSelect)`
2728
const CHART_OPTIONS = [
2829
{ text: "Staked PNK", value: "stakedPNK" },
2930
{ text: "Cases", value: "cases" },
30-
{ text: "Cases per court", value: 2 },
31+
{ text: "Cases per court", value: "casesPerCourt" },
3132
];
3233

3334
const ChartOptionsDropdown: React.FC<{
@@ -56,6 +57,8 @@ const Chart: React.FC = () => {
5657
const [chartOption, setChartOption] = useState("stakedPNK");
5758
const { data } = useHomePageContext();
5859
const chartData = data?.counters;
60+
const courtsChartData = data?.courts;
61+
5962
const processedData = chartData?.reduce((accData: IChartData[], counter) => {
6063
return [
6164
...accData,
@@ -66,10 +69,34 @@ const Chart: React.FC = () => {
6669
];
6770
}, []);
6871

72+
const processedCourtsData = courtsChartData?.reduce(
73+
(accData: CasesByCourtsChartData, current) => {
74+
return {
75+
labels: [...accData.labels, current.name ?? ""],
76+
cases: [...accData.cases, current.numberDisputes],
77+
totalCases: accData.totalCases + parseInt(current.numberDisputes, 10),
78+
};
79+
},
80+
{ labels: [], cases: [], totalCases: 0 }
81+
);
82+
83+
const ChartComponent = useMemo(() => {
84+
switch (chartOption) {
85+
case "casesPerCourt":
86+
return processedCourtsData ? (
87+
<CasesByCourtsChart data={processedCourtsData} />
88+
) : (
89+
<StyledSkeleton height={233} />
90+
);
91+
default:
92+
return processedData ? <TimeSeriesChart data={processedData} /> : <StyledSkeleton height={233} />;
93+
}
94+
}, [processedCourtsData, processedData, chartOption]);
95+
6996
return (
7097
<Container>
7198
<ChartOptionsDropdown {...{ setChartOption }} />
72-
{processedData ? <TimeSeriesChart data={processedData} /> : <StyledSkeleton height={233} />}
99+
{ChartComponent}
73100
</Container>
74101
);
75102
};

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

+12
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,18 @@ const TimeSeriesChart: React.FC<ITimeSeriesChart> = ({ data }) => {
4949
suggestedMin: 0,
5050
},
5151
},
52+
plugins: {
53+
tooltip: {
54+
backgroundColor: theme.whiteBackground,
55+
titleColor: theme.primaryText,
56+
borderColor: theme.stroke,
57+
borderWidth: 1,
58+
displayColors: false,
59+
callbacks: {
60+
labelTextColor: () => theme.primaryText,
61+
},
62+
},
63+
},
5264
};
5365

5466
return (

yarn.lock

+10
Original file line numberDiff line numberDiff line change
@@ -6653,6 +6653,7 @@ __metadata:
66536653
amqplib: "npm:^0.10.3"
66546654
chart.js: "npm:^3.9.1"
66556655
chartjs-adapter-moment: "npm:^1.0.1"
6656+
chartjs-plugin-datalabels: "npm:^2.2.0"
66566657
core-js: "npm:^3.35.0"
66576658
eslint: "npm:^8.56.0"
66586659
eslint-config-prettier: "npm:^8.10.0"
@@ -15309,6 +15310,15 @@ __metadata:
1530915310
languageName: node
1531015311
linkType: hard
1531115312

15313+
"chartjs-plugin-datalabels@npm:^2.2.0":
15314+
version: 2.2.0
15315+
resolution: "chartjs-plugin-datalabels@npm:2.2.0"
15316+
peerDependencies:
15317+
chart.js: ">=3.0.0"
15318+
checksum: e87c2f30d4f6f84b4b1a28c00d1b032d0100f87f8f296a498507047bd2f7fe98eced583a2c09f30e55806e79a0ca7e0db027cc3bed9b27688980e55701a6945f
15319+
languageName: node
15320+
linkType: hard
15321+
1531215322
"check-error@npm:^1.0.2":
1531315323
version: 1.0.2
1531415324
resolution: "check-error@npm:1.0.2"

0 commit comments

Comments
 (0)