Compare commits
No commits in common. "577fcb0e0d7323ef1257f52cfe8e53104e266362" and "a15893ea5632383b62e0006e5a977651f1733d60" have entirely different histories.
577fcb0e0d
...
a15893ea56
@ -4,7 +4,7 @@
|
|||||||
import { Chart, registerables } from "chart.js";
|
import { Chart, registerables } from "chart.js";
|
||||||
import { Line } from "react-chartjs-2";
|
import { Line } from "react-chartjs-2";
|
||||||
import { useIsMobile } from "@/hooks/use-is-mobile";
|
import { useIsMobile } from "@/hooks/use-is-mobile";
|
||||||
import { formatDateMinimal, getDaysAgoDate, parseDate } from "@ssr/common/utils/time-utils";
|
import { formatDateMinimal, getDaysAgo, getDaysAgoDate, parseDate } from "@ssr/common/utils/time-utils";
|
||||||
|
|
||||||
Chart.register(...registerables);
|
Chart.register(...registerables);
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ export type Axis = {
|
|||||||
position?: AxisPosition;
|
position?: AxisPosition;
|
||||||
display?: boolean;
|
display?: boolean;
|
||||||
grid?: { color?: string; drawOnChartArea?: boolean };
|
grid?: { color?: string; drawOnChartArea?: boolean };
|
||||||
title?: { display: boolean; text?: string; color?: string };
|
title?: { display: boolean; text: string; color?: string };
|
||||||
ticks?: {
|
ticks?: {
|
||||||
stepSize?: number;
|
stepSize?: number;
|
||||||
callback?: (value: number, index: number, values: any) => string;
|
callback?: (value: number, index: number, values: any) => string;
|
||||||
@ -34,7 +34,6 @@ export type Dataset = {
|
|||||||
lineTension: number;
|
lineTension: number;
|
||||||
spanGaps: boolean;
|
spanGaps: boolean;
|
||||||
yAxisID: string;
|
yAxisID: string;
|
||||||
hidden?: boolean;
|
|
||||||
type?: DatasetDisplayType;
|
type?: DatasetDisplayType;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -47,17 +46,16 @@ export type DatasetConfig = {
|
|||||||
reverse: boolean;
|
reverse: boolean;
|
||||||
display: boolean;
|
display: boolean;
|
||||||
hideOnMobile?: boolean;
|
hideOnMobile?: boolean;
|
||||||
displayName?: string;
|
displayName: string;
|
||||||
position: AxisPosition;
|
position: AxisPosition;
|
||||||
valueFormatter?: (value: number) => string;
|
valueFormatter?: (value: number) => string; // Added precision option here
|
||||||
};
|
};
|
||||||
type?: DatasetDisplayType;
|
type?: DatasetDisplayType;
|
||||||
labelFormatter: (value: number) => string;
|
labelFormatter: (value: number) => string;
|
||||||
showLegend?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ChartProps = {
|
export type ChartProps = {
|
||||||
labels: Date[] | string[];
|
labels: Date[];
|
||||||
datasetConfig: DatasetConfig[];
|
datasetConfig: DatasetConfig[];
|
||||||
histories: Record<string, (number | null)[]>;
|
histories: Record<string, (number | null)[]>;
|
||||||
};
|
};
|
||||||
@ -67,7 +65,7 @@ const generateAxis = (
|
|||||||
reverse: boolean,
|
reverse: boolean,
|
||||||
display: boolean,
|
display: boolean,
|
||||||
position: AxisPosition,
|
position: AxisPosition,
|
||||||
displayName?: string,
|
displayName: string,
|
||||||
valueFormatter?: (value: number) => string
|
valueFormatter?: (value: number) => string
|
||||||
): Axis => ({
|
): Axis => ({
|
||||||
id,
|
id,
|
||||||
@ -90,7 +88,6 @@ const generateDataset = (
|
|||||||
data: (number | null)[],
|
data: (number | null)[],
|
||||||
borderColor: string,
|
borderColor: string,
|
||||||
yAxisID: string,
|
yAxisID: string,
|
||||||
showLegend: boolean = true,
|
|
||||||
type?: DatasetDisplayType
|
type?: DatasetDisplayType
|
||||||
): Dataset => ({
|
): Dataset => ({
|
||||||
label,
|
label,
|
||||||
@ -100,7 +97,6 @@ const generateDataset = (
|
|||||||
lineTension: 0.5,
|
lineTension: 0.5,
|
||||||
spanGaps: false,
|
spanGaps: false,
|
||||||
yAxisID,
|
yAxisID,
|
||||||
hidden: !showLegend, // Use hidden to disable legend
|
|
||||||
type,
|
type,
|
||||||
...(type === "bar" && {
|
...(type === "bar" && {
|
||||||
backgroundColor: borderColor,
|
backgroundColor: borderColor,
|
||||||
@ -116,6 +112,7 @@ export default function GenericChart({ labels, datasetConfig, histories }: Chart
|
|||||||
reverse: false,
|
reverse: false,
|
||||||
ticks: {
|
ticks: {
|
||||||
font: (context: any) => {
|
font: (context: any) => {
|
||||||
|
// Make the first of the month bold
|
||||||
if (parseDate(context.tick.label).getDate() === 1) {
|
if (parseDate(context.tick.label).getDate() === 1) {
|
||||||
return {
|
return {
|
||||||
weight: "bold",
|
weight: "bold",
|
||||||
@ -146,14 +143,7 @@ export default function GenericChart({ labels, datasetConfig, histories }: Chart
|
|||||||
config.axisConfig.valueFormatter
|
config.axisConfig.valueFormatter
|
||||||
);
|
);
|
||||||
|
|
||||||
return generateDataset(
|
return generateDataset(config.title, historyArray, config.color, config.axisId, config.type || "line");
|
||||||
config.title,
|
|
||||||
historyArray,
|
|
||||||
config.color,
|
|
||||||
config.axisId,
|
|
||||||
config.showLegend !== false, // Respect showLegend property
|
|
||||||
config.type || "line"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -167,15 +157,27 @@ export default function GenericChart({ labels, datasetConfig, histories }: Chart
|
|||||||
scales: axes,
|
scales: axes,
|
||||||
elements: { point: { radius: 0 } },
|
elements: { point: { radius: 0 } },
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: {
|
legend: { position: "top", labels: { color: "white" } },
|
||||||
position: "top",
|
tooltip: {
|
||||||
labels: {
|
callbacks: {
|
||||||
color: "white",
|
title(context: any) {
|
||||||
// Filter out datasets where showLegend is false
|
const date = labels[context[0].dataIndex];
|
||||||
filter: (legendItem: any, chartData: any) => {
|
const differenceInDays = getDaysAgo(date);
|
||||||
// Access showLegend from chartData.datasets
|
let formattedDate: string;
|
||||||
const dataset = chartData.datasets[legendItem.datasetIndex];
|
if (differenceInDays === 0) {
|
||||||
return dataset.showLegend !== false; // Only show if showLegend is not false
|
formattedDate = "Now";
|
||||||
|
} else if (differenceInDays === 1) {
|
||||||
|
formattedDate = "Yesterday";
|
||||||
|
} else {
|
||||||
|
formattedDate = formatDateMinimal(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${formattedDate} ${differenceInDays > 0 ? `(${differenceInDays} day${differenceInDays > 1 ? "s" : ""} ago)` : ""}`;
|
||||||
|
},
|
||||||
|
label(context: any) {
|
||||||
|
const value = Number(context.parsed.y);
|
||||||
|
const config = datasetConfig.find(cfg => cfg.title === context.dataset.label);
|
||||||
|
return config?.labelFormatter(value) ?? "";
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -183,9 +185,6 @@ export default function GenericChart({ labels, datasetConfig, histories }: Chart
|
|||||||
};
|
};
|
||||||
|
|
||||||
const formattedLabels = labels.map(date => {
|
const formattedLabels = labels.map(date => {
|
||||||
if (typeof date === "string") {
|
|
||||||
return date;
|
|
||||||
}
|
|
||||||
const formattedDate = formatDateMinimal(date);
|
const formattedDate = formatDateMinimal(date);
|
||||||
if (formatDateMinimal(getDaysAgoDate(0)) === formattedDate) {
|
if (formatDateMinimal(getDaysAgoDate(0)) === formattedDate) {
|
||||||
return "Now";
|
return "Now";
|
||||||
|
@ -9,7 +9,6 @@ import { useQuery } from "@tanstack/react-query";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { fetchLeaderboard } from "@ssr/common/utils/leaderboard.util";
|
import { fetchLeaderboard } from "@ssr/common/utils/leaderboard.util";
|
||||||
import LeaderboardScoresResponse from "@ssr/common/response/leaderboard-scores-response";
|
import LeaderboardScoresResponse from "@ssr/common/response/leaderboard-scores-response";
|
||||||
import LeaderboardPpChart from "@/components/leaderboard/leaderboard-pp-chart";
|
|
||||||
|
|
||||||
const REFRESH_INTERVAL = 1000 * 60 * 5;
|
const REFRESH_INTERVAL = 1000 * 60 * 5;
|
||||||
|
|
||||||
@ -49,21 +48,17 @@ export function LeaderboardData({ initialLeaderboard, initialScores, initialPage
|
|||||||
}
|
}
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
const leaderboard = currentLeaderboard.leaderboard;
|
|
||||||
return (
|
return (
|
||||||
<main className="flex flex-col-reverse xl:flex-row w-full gap-2">
|
<main className="flex flex-col-reverse xl:flex-row w-full gap-2">
|
||||||
<LeaderboardScores
|
<LeaderboardScores
|
||||||
leaderboard={leaderboard}
|
leaderboard={currentLeaderboard.leaderboard}
|
||||||
initialScores={initialScores}
|
initialScores={initialScores}
|
||||||
initialPage={initialPage}
|
initialPage={initialPage}
|
||||||
leaderboardChanged={newId => setCurrentLeaderboardId(newId)}
|
leaderboardChanged={newId => setCurrentLeaderboardId(newId)}
|
||||||
showDifficulties
|
showDifficulties
|
||||||
isLeaderboardPage
|
isLeaderboardPage
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-col gap-2 w-full xl:w-[500px]">
|
<LeaderboardInfo leaderboard={currentLeaderboard.leaderboard} beatSaverMap={currentLeaderboard.beatsaver} />
|
||||||
<LeaderboardInfo leaderboard={leaderboard} beatSaverMap={currentLeaderboard.beatsaver} />
|
|
||||||
{leaderboard.stars > 0 && <LeaderboardPpChart leaderboard={leaderboard} />}
|
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ type LeaderboardInfoProps = {
|
|||||||
|
|
||||||
export function LeaderboardInfo({ leaderboard, beatSaverMap }: LeaderboardInfoProps) {
|
export function LeaderboardInfo({ leaderboard, beatSaverMap }: LeaderboardInfoProps) {
|
||||||
return (
|
return (
|
||||||
<Card className="w-full h-fit">
|
<Card className="xl:w-[500px] h-fit w-full">
|
||||||
<div className="flex flex-row justify-between w-full">
|
<div className="flex flex-row justify-between w-full">
|
||||||
<div className="flex flex-col justify-between w-full min-h-[160px]">
|
<div className="flex flex-col justify-between w-full min-h-[160px]">
|
||||||
{/* Song Info */}
|
{/* Song Info */}
|
||||||
|
@ -1,53 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
import GenericChart, { DatasetConfig } from "@/components/chart/generic-chart";
|
|
||||||
import ScoreSaberLeaderboard from "@ssr/common/leaderboard/impl/scoresaber-leaderboard";
|
|
||||||
import { scoresaberService } from "@ssr/common/service/impl/scoresaber";
|
|
||||||
import Card from "@/components/card";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
/**
|
|
||||||
* The player the chart is for
|
|
||||||
*/
|
|
||||||
leaderboard: ScoreSaberLeaderboard;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function LeaderboardPpChart({ leaderboard }: Props) {
|
|
||||||
const histories: Record<string, (number | null)[]> = {};
|
|
||||||
const labels: string[] = [];
|
|
||||||
|
|
||||||
for (let accuracy = 60; accuracy <= 100; accuracy += 0.2) {
|
|
||||||
const label = accuracy.toFixed(2) + "%";
|
|
||||||
labels.push(label);
|
|
||||||
|
|
||||||
const history = histories["pp"];
|
|
||||||
if (!history) {
|
|
||||||
histories["pp"] = [];
|
|
||||||
}
|
|
||||||
histories["pp"].push(scoresaberService.getPp(leaderboard.stars, accuracy));
|
|
||||||
}
|
|
||||||
|
|
||||||
const datasetConfig: DatasetConfig[] = [
|
|
||||||
{
|
|
||||||
title: "PP",
|
|
||||||
field: "pp",
|
|
||||||
color: "#3EC1D3",
|
|
||||||
axisId: "y",
|
|
||||||
axisConfig: {
|
|
||||||
reverse: false,
|
|
||||||
display: true,
|
|
||||||
displayName: "PP",
|
|
||||||
position: "left",
|
|
||||||
},
|
|
||||||
labelFormatter: (value: number) => `${value.toFixed(2)}pp`,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card className="h-64 w-full">
|
|
||||||
<p className="font-semibold">PP Curve</p>
|
|
||||||
<GenericChart labels={labels} datasetConfig={datasetConfig} histories={histories} />
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
Reference in New Issue
Block a user