forked from Minecraft/Stonks
parent
6512f3ca41
commit
a03bd8ff8d
|
@ -0,0 +1,98 @@
|
|||
import React from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import axios from "axios";
|
||||
|
||||
import {
|
||||
AspectRatio,
|
||||
Box,
|
||||
Flex,
|
||||
Heading,
|
||||
HStack,
|
||||
Icon,
|
||||
SimpleGrid,
|
||||
Stat,
|
||||
StatLabel,
|
||||
StatNumber,
|
||||
StatHelpText,
|
||||
StatArrow,
|
||||
StatGroup,
|
||||
Tooltip,
|
||||
} from "@chakra-ui/react";
|
||||
import { FaUserCircle, FaArrowAltCircleRight } from "react-icons/fa";
|
||||
import { Link } from "react-router-dom";
|
||||
import { AutoSizer, WindowScroller, List } from "react-virtualized";
|
||||
import prettifyStatisticName from "./PrettifyStatisticName";
|
||||
import StackedBar from "./StackedBar";
|
||||
|
||||
const AggregatesShowcase = () => {
|
||||
const aggregates = useQuery(
|
||||
`aggregates`,
|
||||
async () => {
|
||||
const { data } = await axios.get(`http://localhost:7000/api/aggregates`);
|
||||
return data;
|
||||
},
|
||||
{
|
||||
placeholderData: [],
|
||||
}
|
||||
);
|
||||
|
||||
const killedAggregates = aggregates.data
|
||||
.filter((x) => x.type === "minecraft:killed")
|
||||
.sort((a, b) => b.value - a.value);
|
||||
const killedTotal = killedAggregates.reduce((acc, val) => acc + val.value, 0);
|
||||
|
||||
const killedByAggregates = aggregates.data
|
||||
.filter((x) => x.type === "minecraft:killed_by")
|
||||
.sort((a, b) => b.value - a.value);
|
||||
const killedByTotal = killedByAggregates.reduce(
|
||||
(acc, val) => acc + val.value,
|
||||
0
|
||||
);
|
||||
|
||||
const travelAggregates = aggregates.data
|
||||
.filter((x) => x.type === "minecraft:custom" && x.name.endsWith("one_cm"))
|
||||
.sort((a, b) => b.value - a.value);
|
||||
const travelTotal = travelAggregates.reduce((acc, val) => acc + val.value, 0);
|
||||
|
||||
return aggregates.isFetched ? (
|
||||
<>
|
||||
<StackedBar heading="Mobs Killed" aggregates={killedAggregates} />
|
||||
|
||||
<StackedBar heading="Causes of Death" aggregates={killedByAggregates} />
|
||||
|
||||
<StackedBar heading="Preferred Travel" aggregates={travelAggregates} />
|
||||
|
||||
<SimpleGrid columns={2} mt={4} spacingY={4} spacingX={8}>
|
||||
{aggregates.data
|
||||
.filter(
|
||||
(x) =>
|
||||
x.type !== "minecraft:killed" &&
|
||||
x.type !== "minecraft:killed_by" &&
|
||||
!x.name.endsWith("one_cm")
|
||||
)
|
||||
.map((x, i) => {
|
||||
var value = x.value;
|
||||
if (x.name === "minecraft:play_one_minute") {
|
||||
value = x.value / 20 / 60;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stat key={i}>
|
||||
<StatLabel>{prettifyStatisticName(x.type, x.name)}</StatLabel>
|
||||
<StatNumber>{`${x.value
|
||||
.toString()
|
||||
.replace(
|
||||
/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g,
|
||||
","
|
||||
)}`}</StatNumber>
|
||||
</Stat>
|
||||
);
|
||||
})}
|
||||
</SimpleGrid>
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
};
|
||||
|
||||
export default AggregatesShowcase;
|
|
@ -6,6 +6,7 @@ import { FaUserCircle, FaArrowAltCircleRight } from "react-icons/fa";
|
|||
import { Link } from "react-router-dom";
|
||||
import { AutoSizer, WindowScroller, List } from "react-virtualized";
|
||||
import prettifyStatisticName from "./PrettifyStatisticName";
|
||||
import AggregatesShowcase from "./AggregatesShowcase";
|
||||
|
||||
const Search = ({ statistics, players }) => {
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
|
@ -100,8 +101,11 @@ const Search = ({ statistics, players }) => {
|
|||
variant="filled"
|
||||
value={searchTerm}
|
||||
onChange={(event) => setSearchTerm(event.target.value)}
|
||||
mb="8"
|
||||
/>
|
||||
{searchResults.length === 0 ? <AggregatesShowcase /> : <></>}
|
||||
<div ref={registerChild}>
|
||||
{searchResults.length > 0 ? (
|
||||
<AutoSizer disableHeight>
|
||||
{({ width }) => (
|
||||
<List
|
||||
|
@ -117,6 +121,9 @@ const Search = ({ statistics, players }) => {
|
|||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
import React, { useMemo } from "react";
|
||||
import useHover from "./useHover";
|
||||
import {
|
||||
AspectRatio,
|
||||
Box,
|
||||
Button,
|
||||
Heading,
|
||||
HStack,
|
||||
Icon,
|
||||
Input,
|
||||
Stat,
|
||||
StatLabel,
|
||||
StatNumber,
|
||||
StatHelpText,
|
||||
StatArrow,
|
||||
StatGroup,
|
||||
Tooltip,
|
||||
} from "@chakra-ui/react";
|
||||
import prettifyStatisticName from "./PrettifyStatisticName";
|
||||
|
||||
const randomColor = () =>
|
||||
`hsl(${Math.floor(Math.random() * 359)}, ${
|
||||
50 + Math.floor(Math.random() * 50)
|
||||
}%, ${25 + Math.floor(Math.random() * 50)}%)`;
|
||||
|
||||
const StackedBarSegment = ({ aggregate, total }) => {
|
||||
const color = useMemo(randomColor, []);
|
||||
|
||||
const [hoverRef, isHovered] = useHover();
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
hasArrow
|
||||
label={`${aggregate.value
|
||||
.toString()
|
||||
.replace(
|
||||
/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g,
|
||||
","
|
||||
)} ${prettifyStatisticName(aggregate.type, aggregate.name)}`}
|
||||
>
|
||||
<Box
|
||||
width={aggregate.value / total}
|
||||
height="100%"
|
||||
filter={isHovered ? "clear" : "saturate(0.15)"}
|
||||
backgroundColor={color}
|
||||
ref={hoverRef}
|
||||
></Box>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const StackedBar = ({ heading, aggregates }) => {
|
||||
const total = aggregates.reduce((acc, val) => acc + val.value, 0);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Heading size="xs" mt="4" fontWeight="medium">
|
||||
{heading}
|
||||
</Heading>
|
||||
<HStack
|
||||
width="100%"
|
||||
height="8"
|
||||
spacing="0"
|
||||
rounded="md"
|
||||
overflow="hidden"
|
||||
mt="2"
|
||||
>
|
||||
{aggregates.map((x, i) => (
|
||||
<StackedBarSegment key={i} aggregate={x} total={total} />
|
||||
))}
|
||||
</HStack>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default StackedBar;
|
|
@ -0,0 +1,25 @@
|
|||
import React, { useEffect, useRef, useState } from "react";
|
||||
|
||||
function useHover() {
|
||||
const [value, setValue] = useState(false);
|
||||
const ref = useRef(null);
|
||||
const handleMouseOver = () => setValue(true);
|
||||
const handleMouseOut = () => setValue(false);
|
||||
useEffect(
|
||||
() => {
|
||||
const node = ref.current;
|
||||
if (node) {
|
||||
node.addEventListener("mouseover", handleMouseOver);
|
||||
node.addEventListener("mouseout", handleMouseOut);
|
||||
return () => {
|
||||
node.removeEventListener("mouseover", handleMouseOver);
|
||||
node.removeEventListener("mouseout", handleMouseOut);
|
||||
};
|
||||
}
|
||||
},
|
||||
[ref.current] // Recall only if ref changes
|
||||
);
|
||||
return [ref, value];
|
||||
}
|
||||
|
||||
export default useHover;
|
Loading…
Reference in New Issue