Add aggregate statistics showcase to search view

main release-0.2
Kevin Belisle 2021-07-12 17:47:27 -04:00
parent 6512f3ca41
commit a03bd8ff8d
4 changed files with 221 additions and 15 deletions

View File

@ -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;

View File

@ -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,23 +101,29 @@ const Search = ({ statistics, players }) => {
variant="filled"
value={searchTerm}
onChange={(event) => setSearchTerm(event.target.value)}
mb="8"
/>
{searchResults.length === 0 ? <AggregatesShowcase /> : <></>}
<div ref={registerChild}>
<AutoSizer disableHeight>
{({ width }) => (
<List
autoHeight
height={height}
isScrolling={isScrolling}
onScroll={onChildScroll}
scrollTop={scrollTop}
rowCount={searchResults.length}
rowHeight={36}
rowRenderer={renderRow}
width={width}
/>
)}
</AutoSizer>
{searchResults.length > 0 ? (
<AutoSizer disableHeight>
{({ width }) => (
<List
autoHeight
height={height}
isScrolling={isScrolling}
onScroll={onChildScroll}
scrollTop={scrollTop}
rowCount={searchResults.length}
rowHeight={36}
rowRenderer={renderRow}
width={width}
/>
)}
</AutoSizer>
) : (
<></>
)}
</div>
</>
)}

View File

@ -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;

View File

@ -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;