Merge pull request #285 from Hitjack007/tankopedia-scale

This commit is contained in:
TrèsAbhi authored and GitHub committed 2026-11-01 13:23:50 -06:00
commit 15a08b934b
2 files changed
+121 -8

No files matched your search

@@ -1,5 +1,5 @@
import { createDefaultSkills } from "@blitzkit/core";
import { Flex, Progress, Text } from "@radix-ui/themes";
import { Box, Flex, Progress, Text } from "@radix-ui/themes";
import { clamp } from "lodash-es";
import { memo, useMemo, type ComponentProps, type ReactNode } from "react";
import { api } from "../../../../../../core/blitzkit/api";
@@ -16,6 +16,7 @@ import {
TankopediaRelativeAgainst,
} from "../../../../../../stores/tankopedia";
import { Info, type InfoProps } from "./Info";
import { StatScale } from "./StatScale";
type InfoWithDeltaProps = Omit<InfoProps, "name"> & {
stats: TankCharacteristics;
@@ -122,6 +123,15 @@ export const InfoWithDelta = memo<InfoWithDeltaProps>(
return othersValue !== undefined;
}) as TankCharacteristics[];
}, [relativeAgainst, shellIndex, equipmentMatrix, equalize]);
const values = useMemo(
() =>
others.map((tank) =>
typeof props.value === "function"
? props.value(tank)!
: (tank[props.value] as number),
),
[others],
);
const betterTanks = others.filter((tank) => {
const othersValue =
typeof props.value === "function"
@@ -161,13 +171,25 @@ export const InfoWithDelta = memo<InfoWithDeltaProps>(
{!noRanking && (
<Flex pl={indent ? "2" : "0"} align="center" gap="2">
<Progress
variant="soft"
size="1"
value={goodness * 100}
color={color}
style={{ height: "0.125rem", opacity: 0.5 }}
/>
<Box position="relative" flexGrow="1">
<StatScale
values={values}
value={uhWhatDoICallThisVariable}
lowerIsBetter={deltaType === "lowerIsBetter"}
/>
<Progress
variant="soft"
size="1"
value={goodness * 100}
color={color}
style={{
position: "relative",
height: "0.125rem",
opacity: 0.75,
}}
/>
</Box>
<Text color="gray" size="1">
{clamp(betterTanks.length + 1, 1, others.length)} /{" "}
@@ -0,0 +1,91 @@
import { memo, useEffect, useMemo, useRef } from "react";
const RESOLUTION = 1000;
const HEIGHT = 8;
const DENSE_TICKS = 100;
interface StatScaleProps {
values: number[];
value: number;
lowerIsBetter?: boolean;
}
export const StatScale = memo<StatScaleProps>(
({ values, value, lowerIsBetter }) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const ticks = useMemo(() => {
const min = Math.min(value, ...values);
const max = Math.max(value, ...values);
const range = max - min;
const positions = new Set<number>();
for (const other of values) {
const raw = range === 0 ? 0.5 : (other - min) / range;
const x = Math.round((lowerIsBetter ? 1 - raw : raw) * RESOLUTION);
if (x === 0 || x === RESOLUTION) continue;
positions.add(x);
}
return Array.from(positions, (x) => ({ x }));
}, [values, value, lowerIsBetter]);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const draw = () => {
const { width } = canvas.getBoundingClientRect();
const pixelRatio = window.devicePixelRatio || 1;
const canvasWidth = Math.ceil(width * pixelRatio);
const canvasHeight = Math.ceil(HEIGHT * pixelRatio);
if (canvas.width !== canvasWidth || canvas.height !== canvasHeight) {
canvas.width = canvasWidth;
canvas.height = canvasHeight;
}
const context = canvas.getContext("2d");
if (!context) return;
context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
context.clearRect(0, 0, width, HEIGHT);
const styles = getComputedStyle(canvas);
for (const { x } of ticks) {
context.fillStyle = styles.getPropertyValue("--gray-a6").trim();
context.fillRect((x / RESOLUTION) * width, 0, 1, HEIGHT);
}
};
const observer = new ResizeObserver(draw);
observer.observe(canvas);
window.addEventListener("resize", draw);
draw();
return () => {
observer.disconnect();
window.removeEventListener("resize", draw);
};
}, [ticks]);
return (
<canvas
ref={canvasRef}
aria-hidden="true"
style={{
position: "absolute",
top: 0,
bottom: 0,
left: 0,
width: "100%",
height: HEIGHT,
margin: "auto 0",
opacity: Math.min(1, Math.sqrt(DENSE_TICKS / ticks.length)),
pointerEvents: "none",
}}
/>
);
},
);