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 { Link } from "react-router-dom";
|
||||||
import { AutoSizer, WindowScroller, List } from "react-virtualized";
|
import { AutoSizer, WindowScroller, List } from "react-virtualized";
|
||||||
import prettifyStatisticName from "./PrettifyStatisticName";
|
import prettifyStatisticName from "./PrettifyStatisticName";
|
||||||
|
import AggregatesShowcase from "./AggregatesShowcase";
|
||||||
|
|
||||||
const Search = ({ statistics, players }) => {
|
const Search = ({ statistics, players }) => {
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
|
@ -100,8 +101,11 @@ const Search = ({ statistics, players }) => {
|
||||||
variant="filled"
|
variant="filled"
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(event) => setSearchTerm(event.target.value)}
|
onChange={(event) => setSearchTerm(event.target.value)}
|
||||||
|
mb="8"
|
||||||
/>
|
/>
|
||||||
|
{searchResults.length === 0 ? <AggregatesShowcase /> : <></>}
|
||||||
<div ref={registerChild}>
|
<div ref={registerChild}>
|
||||||
|
{searchResults.length > 0 ? (
|
||||||
<AutoSizer disableHeight>
|
<AutoSizer disableHeight>
|
||||||
{({ width }) => (
|
{({ width }) => (
|
||||||
<List
|
<List
|
||||||
|
@ -117,6 +121,9 @@ const Search = ({ statistics, players }) => {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</AutoSizer>
|
</AutoSizer>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
</div>
|
</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