feat: rough folder avgs

This commit is contained in:
zkldi
2023-03-12 15:47:49 +00:00
parent bc41dbf0cd
commit f76b1eec7e
2 changed files with 211 additions and 24 deletions
@@ -4,12 +4,14 @@ import Divider from "components/util/Divider";
import Icon from "components/util/Icon";
import SelectButton from "components/util/SelectButton";
import { useBucket } from "components/util/useBucket";
import React, { useState } from "react";
import { GetGamePTConfig, GetScoreEnumConfs, UserDocument } from "tachi-common";
import React, { useMemo, useState } from "react";
import { GPTString, GetGamePTConfig, GetScoreEnumConfs, UserDocument } from "tachi-common";
import { GamePT } from "types/react";
import { FolderDataset } from "types/tables";
import { GPT_CLIENT_IMPLEMENTATIONS } from "lib/game-implementations";
import FolderMinimap from "./FolderMinimap";
import FolderScoreDistributionChart from "./FolderScoreDistributionChart";
import FolderScoreAverages from "./FolderScoreAverages";
export default function FolderInfoHeader({
game,
@@ -23,53 +25,67 @@ export default function FolderInfoHeader({
folderTitle: string;
} & GamePT) {
const preferredDefaultEnum = useBucket(game, playtype);
const [enumMetric, setEnumMetric] = useState<string>(preferredDefaultEnum);
const [breakdownInfo, setBreakdownInfo] = useState<"graph" | "switchboard">("switchboard");
const [currentGraph, setCurrentGraph] = useState<string>(`${preferredDefaultEnum}-stats`);
const enumGraphs = ["minimap", "stats"];
const gptConfig = GetGamePTConfig(game, playtype);
const gptImpl = GPT_CLIENT_IMPLEMENTATIONS[`${game}:${playtype}` as GPTString];
const enumConf = GetScoreEnumConfs(gptConfig);
const [metric, type] = useMemo(() => currentGraph.split("-"), [currentGraph]);
return (
<Card header={`${reqUser.username}'s ${folderTitle} Breakdown`}>
<div className="col-12 d-flex justify-content-center">
<div className="btn-group">
{Object.keys(enumConf).map((k) => (
<SelectButton value={enumMetric} setValue={setEnumMetric} id={k}>
<Icon type="chart-area" />
{UppercaseFirst(k)} Info
</SelectButton>
))}
{enumGraphs.flatMap((g) =>
Object.keys(enumConf).flatMap((en) => (
<SelectButton
className={g === "minimap" ? "d-none d-lg-block" : ""}
value={currentGraph}
setValue={setCurrentGraph}
id={`${en}-${g}`}
>
{/* @ts-expect-error zzz */}
<Icon type={gptImpl.enumIcons[en]} />
{UppercaseFirst(en)} {UppercaseFirst(g)}
</SelectButton>
))
)}
<SelectButton value={currentGraph} setValue={setCurrentGraph} id="score">
<Icon type="sort" />
Score Averages
</SelectButton>
</div>
</div>
<div className="col-12 mt-4 d-flex justify-content-center">
<select
className="form-control"
value={breakdownInfo}
onChange={(e) => setBreakdownInfo(e.target.value as any)}
>
<option value="graph">Thermometer</option>
<option value="switchboard">Switchboard</option>
</select>
</div>
<div className="col-12">
<Divider />
</div>
{breakdownInfo === "graph" ? (
{type === "stats" ? (
<FolderScoreDistributionChart
game={game}
folderDataset={folderDataset}
playtype={playtype}
view={enumMetric}
view={metric}
/>
) : (
) : type === "minimap" ? (
<FolderMinimap
reqUser={reqUser}
game={game}
playtype={playtype}
folderDataset={folderDataset}
enumMetric={enumMetric}
enumMetric={metric}
/>
) : (
<FolderScoreAverages
reqUser={reqUser}
game={game}
playtype={playtype}
folderDataset={folderDataset}
/>
)}
</Card>
@@ -0,0 +1,171 @@
import { NumericSOV } from "util/sorts";
import { UppercaseFirst } from "util/misc";
import React, { useMemo } from "react";
import { GetGamePTConfig, integer } from "tachi-common";
import { ConfDecimalScoreMetric, ConfIntegerScoreMetric } from "tachi-common/types/metrics";
import { UGPT } from "types/react";
import { FolderDataset } from "types/tables";
import DebugContent from "components/util/DebugContent";
import MiniTable from "components/tables/components/MiniTable";
import Muted from "components/util/Muted";
import DifficultyCell from "components/tables/cells/DifficultyCell";
import TitleCell from "components/tables/cells/TitleCell";
import ScoreCoreCells from "components/tables/game-core-cells/ScoreCoreCells";
import SongChartInfoFormat from "../songs/SongInfoFormat";
export default function FolderScoreAverages({
folderDataset,
game,
playtype,
reqUser,
}: UGPT & { folderDataset: FolderDataset }) {
const gptConfig = GetGamePTConfig(game, playtype);
const metrics = {
...gptConfig.providedMetrics,
...gptConfig.derivedMetrics,
};
const entries = Object.entries(metrics);
const relevantScoreMetrics = entries
.filter(([k, v]) => {
if (v.type !== "DECIMAL" && v.type !== "INTEGER") {
return false;
}
const v2 = v as ConfIntegerScoreMetric | ConfDecimalScoreMetric;
if (v2.chartDependentMax) {
return false;
}
return true;
})
.map((e) => e[0]);
const { unplayed, played, data } = useMemo(() => {
const data: Record<string, any> = {};
const played = folderDataset.filter((e) => e.__related.pb);
const unplayed = folderDataset.length - played.length;
for (const metric of relevantScoreMetrics) {
const total = played.reduce(
// @ts-expect-error accessing property unchecked oh nooo
(acc, e) => acc + e.__related.pb?.scoreData[metric] ?? 0,
0
);
const sorted = played.sort(
// @ts-expect-error accessing property unchecked oh nooo
NumericSOV((k) => k.__related.pb?.scoreData[metric] ?? -Infinity)
);
const worst = sorted[0];
const best = sorted[sorted.length - 1];
let avgAll = total / folderDataset.length;
let avgPlayed = total / played.length;
if (metrics[metric].type === "INTEGER") {
avgAll = Math.floor(avgAll);
avgPlayed = Math.floor(avgPlayed);
}
data[metric] = {
avgAll,
avgPlayed,
worst,
best,
};
}
return {
played,
unplayed,
data,
};
}, [folderDataset, relevantScoreMetrics]);
if (relevantScoreMetrics.length === 0) {
return <div>This game has nothing to show you!</div>;
}
if (played.length === 0) {
return <div>You haven't played anything in this folder!</div>;
}
const playRate = (100 * played.length) / folderDataset.length;
return (
<MiniTable className="table-responsive-md" headers={["Score Stats"]} colSpan={100}>
<tr>
<td>Played</td>
<td
colSpan={6}
className={`text-${
playRate === 100 ? "success" : playRate > 50 ? "warning" : "danger"
}`}
>
{playRate.toFixed(2)}%
{unplayed > 0 && (
<>
<br />
<Muted>
({played.length} played, {unplayed} unplayed)
</Muted>
</>
)}
</td>
</tr>
{Object.entries(data).map(([k, v]) => (
<>
<tr>
<td>{UppercaseFirst(k)} Average (Played Charts)</td>
{/* @ts-expect-error this won't fail */}
<td colSpan={6}>{metrics[k].formatter(v.avgPlayed)}</td>
</tr>
<tr>
<td>{UppercaseFirst(k)} Average (All Charts)</td>
{/* @ts-expect-error this won't fail */}
<td colSpan={6}>{metrics[k].formatter(v.avgAll)}</td>
</tr>
<tr>
<td>Best {UppercaseFirst(k)}</td>
<DifficultyCell alwaysShort chart={v.best} game={game} />
<TitleCell
game={game}
song={v.best.__related.song}
chart={v.best}
comment={v.best.__related.pb.comment}
/>
<ScoreCoreCells
game={game}
score={v.best.__related.pb}
short
chart={v.best}
/>
</tr>
<tr>
<td>Worst {UppercaseFirst(k)}</td>
<DifficultyCell alwaysShort chart={v.worst} game={game} />
<TitleCell
game={game}
song={v.worst.__related.song}
chart={v.worst}
comment={v.worst.__related.pb.comment}
/>
<ScoreCoreCells
game={game}
score={v.worst.__related.pb}
short
chart={v.worst}
/>
</tr>
</>
))}
</MiniTable>
);
}