mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-11-04 16:30:37 +01:00
Merge pull request #285 from Hitjack007/tankopedia-scale
This commit is contained in:
2 files changed
+115
-2
No files matched your search
+24
-2
@@ -1,5 +1,5 @@
|
|||||||
import { createDefaultSkills } from "@blitzkit/core";
|
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 { clamp } from "lodash-es";
|
||||||
import { memo, useMemo, type ComponentProps, type ReactNode } from "react";
|
import { memo, useMemo, type ComponentProps, type ReactNode } from "react";
|
||||||
import { api } from "../../../../../../core/blitzkit/api";
|
import { api } from "../../../../../../core/blitzkit/api";
|
||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
TankopediaRelativeAgainst,
|
TankopediaRelativeAgainst,
|
||||||
} from "../../../../../../stores/tankopedia";
|
} from "../../../../../../stores/tankopedia";
|
||||||
import { Info, type InfoProps } from "./Info";
|
import { Info, type InfoProps } from "./Info";
|
||||||
|
import { StatScale } from "./StatScale";
|
||||||
|
|
||||||
type InfoWithDeltaProps = Omit<InfoProps, "name"> & {
|
type InfoWithDeltaProps = Omit<InfoProps, "name"> & {
|
||||||
stats: TankCharacteristics;
|
stats: TankCharacteristics;
|
||||||
@@ -122,6 +123,15 @@ export const InfoWithDelta = memo<InfoWithDeltaProps>(
|
|||||||
return othersValue !== undefined;
|
return othersValue !== undefined;
|
||||||
}) as TankCharacteristics[];
|
}) as TankCharacteristics[];
|
||||||
}, [relativeAgainst, shellIndex, equipmentMatrix, equalize]);
|
}, [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 betterTanks = others.filter((tank) => {
|
||||||
const othersValue =
|
const othersValue =
|
||||||
typeof props.value === "function"
|
typeof props.value === "function"
|
||||||
@@ -161,13 +171,25 @@ export const InfoWithDelta = memo<InfoWithDeltaProps>(
|
|||||||
|
|
||||||
{!noRanking && (
|
{!noRanking && (
|
||||||
<Flex pl={indent ? "2" : "0"} align="center" gap="2">
|
<Flex pl={indent ? "2" : "0"} align="center" gap="2">
|
||||||
|
<Box position="relative" flexGrow="1">
|
||||||
|
<StatScale
|
||||||
|
values={values}
|
||||||
|
value={uhWhatDoICallThisVariable}
|
||||||
|
lowerIsBetter={deltaType === "lowerIsBetter"}
|
||||||
|
/>
|
||||||
|
|
||||||
<Progress
|
<Progress
|
||||||
variant="soft"
|
variant="soft"
|
||||||
size="1"
|
size="1"
|
||||||
value={goodness * 100}
|
value={goodness * 100}
|
||||||
color={color}
|
color={color}
|
||||||
style={{ height: "0.125rem", opacity: 0.5 }}
|
style={{
|
||||||
|
position: "relative",
|
||||||
|
height: "0.125rem",
|
||||||
|
opacity: 0.75,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Text color="gray" size="1">
|
<Text color="gray" size="1">
|
||||||
{clamp(betterTanks.length + 1, 1, others.length)} /{" "}
|
{clamp(betterTanks.length + 1, 1, others.length)} /{" "}
|
||||||
|
|||||||
+91
@@ -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",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
Reference in new issue
Block a user