Skip to content

Commit

Permalink
Histogram
Browse files Browse the repository at this point in the history
  • Loading branch information
SAPikachu committed Jan 16, 2022
1 parent d9b5add commit 660ea52
Show file tree
Hide file tree
Showing 13 changed files with 374 additions and 142 deletions.
9 changes: 1 addition & 8 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,7 @@
"comma-dangle": 0,
"computed-property-spacing": [2, "never"],
"indent": "off",
"@typescript-eslint/indent": [
2,
2,
{
"SwitchCase": 1,
"ignoredNodes": ["ConditionalExpression *", "IntersectionType *"]
}
],
"@typescript-eslint/indent": "off",
"key-spacing": [2, { "afterColon": true }],
"no-mixed-spaces-and-tabs": 2,
"no-trailing-spaces": 2,
Expand Down
5 changes: 3 additions & 2 deletions src/components/gameRecords/player.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { ReadMore } from "@mui/icons-material";
import { Link, Typography, TypographyProps, useTheme } from "@mui/material";
import React from "react";

import { GameRecord, PlayerRecord, getLevelTag } from "../../data/types";
import { generatePlayerPathById } from "./routes";

export const Player = function ({
export const Player = React.memo(function ({
player,
game,
hideDetailIcon,
Expand Down Expand Up @@ -50,4 +51,4 @@ export const Player = function ({
)}
</Typography>
);
};
});
5 changes: 3 additions & 2 deletions src/components/playerDetails/charts/rankRate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const formatLabel = (x: any) => (x.rate > 0 ? x.label : null);
const createLabelLine = (props: any) =>
props.payload.payload.rate > 0 ? <Curve {...props} type="linear" className="recharts-pie-label-line" /> : null;

export default function RankRateChart({ metadata, aspect = 1 }: { metadata: PlayerMetadata; aspect?: number }) {
const RankRateChart = React.memo(function ({ metadata, aspect = 1 }: { metadata: PlayerMetadata; aspect?: number }) {
const { i18n } = useTranslation();
const ranks = useMemo(
() => metadata.rank_rates.map((x, index) => ({ label: getRankLabelByIndex(index), rate: x })),
Expand All @@ -47,4 +47,5 @@ export default function RankRateChart({ metadata, aspect = 1 }: { metadata: Play
</PieChart>
</ResponsiveContainer>
);
}
});
export default RankRateChart;
6 changes: 4 additions & 2 deletions src/components/playerDetails/charts/recentRank.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { calculateDeltaPoint } from "../../../data/types/metadata";
import { useIsMobile } from "../../../utils/index";
import Conf from "../../../utils/conf";
import { alpha, Box, styled, Typography } from "@mui/material";
import React from "react";

declare module "recharts" {
interface DotProps {
Expand Down Expand Up @@ -82,7 +83,7 @@ const RankChartTooltip = ({ active, payload }: TooltipProps = {}) => {
);
};

export default function RecentRankChart({
const RecentRankChart = React.memo(function ({
dataAdapter,
playerId,
aspect = 2,
Expand Down Expand Up @@ -171,4 +172,5 @@ export default function RecentRankChart({
</LineChart>
</ResponsiveContainer>
);
}
});
export default RecentRankChart;
7 changes: 4 additions & 3 deletions src/components/playerDetails/charts/winLoseDistribution.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { PlayerExtendedStats, PlayerMetadata } from "../../../data/types";
import SimplePieChart, { PieChartItem } from "../../charts/simplePieChart";
import { sum } from "../../../utils";
import { formatPercent } from "../../../utils/index";
import { useMemo } from "react";
import React, { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { Box, Typography, useTheme } from "@mui/material";

Expand All @@ -22,7 +22,7 @@ function buildItems(
.filter((item) => item.value);
}

export default function WinLoseDistribution({ stats }: { stats: PlayerExtendedStats; metadata: PlayerMetadata }) {
const WinLoseDistribution = React.memo(function ({ stats }: { stats: PlayerExtendedStats; metadata: PlayerMetadata }) {
const { t } = useTranslation();
const theme = useTheme();
const winData = useMemo(
Expand Down Expand Up @@ -101,4 +101,5 @@ export default function WinLoseDistribution({ stats }: { stats: PlayerExtendedSt
</Box>
</Box>
);
}
});
export default WinLoseDistribution;
112 changes: 112 additions & 0 deletions src/components/playerDetails/histogram.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { Box, Typography, useTheme } from "@mui/material";
import React from "react";
import { Trans, useTranslation } from "react-i18next";
import { getGlobalHistogram } from "../../data/source/misc";

import { GameMode, HistogramData, HistogramGroup, PlayerExtendedStats } from "../../data/types";
import { formatPercent, sum, useAsyncFactory } from "../../utils";
import { useModel } from "../gameRecords/model";

function generatePath(bins: number[], barMax: number, start: number) {
return `M ${start} 0 ` + bins.map((bin) => `h 1 V ${bin / barMax}`).join(" ") + " V 0 Z";
}

function shouldUseClamped(value: number | undefined, data: HistogramGroup) {
return (
typeof value !== "number" ||
(data.histogramClamped && value >= data.histogramClamped.min && value <= data.histogramClamped.max)
);
}
const Histogram = React.memo(function ({ data, value }: { data?: HistogramGroup; value?: number }) {
const theme = useTheme();
if (!data) {
return <></>;
}
const histogram = shouldUseClamped(value, data) ? data.histogramClamped : data.histogramFull;
if (!histogram) {
return <></>;
}
if (value !== undefined) {
value = Math.max(histogram.min, Math.min(histogram.max, value));
}
const barMax = Math.max(...histogram.bins);
const binStep = (histogram.max - histogram.min) / histogram.bins.length;
const splitPoint = value === undefined ? histogram.bins.length : Math.ceil((value - histogram.min) / binStep);
const meanBin = Math.floor((data.mean - histogram.min) / binStep);
return (
<svg width={120} height={40} viewBox={`0 0 ${histogram.bins.length} 1`} preserveAspectRatio="none">
<g transform="scale(1, -1)" transform-origin="center">
<path d={generatePath(histogram.bins.slice(0, splitPoint), barMax, 0)} fill={theme.palette.grey[500]} />
{splitPoint < histogram.bins.length && (
<path d={generatePath(histogram.bins.slice(splitPoint), barMax, splitPoint)} fill={theme.palette.grey[800]} />
)}
{!Number.isInteger(binStep) && histogram.bins.length > 60 && (
<line stroke={theme.palette.grey[50]} x1={meanBin} x2={meanBin} y1={0} y2={1} strokeDasharray={0.1} />
)}
</g>
</svg>
);
});

function getValuePosition(value: number, data: HistogramData) {
const binStep = (data.max - data.min) / data.bins.length;
const bin = Math.floor((value - data.min) / binStep);
if (bin < 0) {
return 0;
}
if (bin >= data.bins.length) {
return sum(data.bins);
}
return sum(data.bins.slice(0, bin)) + data.bins[bin] * ((value - (data.min + binStep * bin)) / binStep);
}

export const StatHistogram = React.memo(function ({
statKey,
value,
valueFormatter,
}: {
statKey: keyof PlayerExtendedStats;
value?: number;
valueFormatter: (value: number) => string;
}) {
const { t } = useTranslation();
const [model] = useModel();
const globalHistogram = useAsyncFactory(() => getGlobalHistogram().catch(() => null), [], "globalHistogram");
if (!globalHistogram || model.type !== "player" || model.selectedModes.length !== 1) {
return <></>;
}
const mode = model.selectedModes[0];
const modeHistogram = globalHistogram[mode];
if (!modeHistogram || !(statKey in modeHistogram["0"])) {
return <></>;
}
const histogramData = modeHistogram["0"][statKey];
if (!histogramData?.histogramFull) {
return <></>;
}
const numTotal = sum(histogramData.histogramFull.bins);
const numPos =
value === undefined
? 0
: shouldUseClamped(value, histogramData) && histogramData.histogramClamped
? getValuePosition(value, histogramData.histogramClamped) +
getValuePosition(histogramData.histogramClamped.min, histogramData.histogramFull)
: getValuePosition(value, histogramData.histogramFull);
return (
<Box>
<Typography variant="inherit" mb={2}>
<Trans defaults="{{mode}}之间平均值:" values={{ mode: t(GameMode[mode]) }} />
{valueFormatter(histogramData.mean)}
</Typography>
<Histogram data={histogramData} value={value} />
{value !== undefined && (
<Typography variant="inherit">
<Trans defaults="{{mode}}之间位置:" values={{ mode: t(GameMode[mode]) }} />
{formatPercent(numPos / numTotal)}
</Typography>
)}
</Box>
);
});

export default Histogram;
Loading

1 comment on commit 660ea52

@vercel
Copy link

@vercel vercel bot commented on 660ea52 Jan 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.