feat: start of charts

This commit is contained in:
tresabhi committed 2024-12-23 12:48:52 -06:00
1 parent b6fd5eaa66
commit eb95230a5f
11 files changed
+192 -165

No files matched your search

@@ -0,0 +1,32 @@
import { Flex, Select, Text } from '@radix-ui/themes';
import { Charts, type ChartsPeriod } from '../../stores/charts';
export function ChartsControl() {
const period = Charts.use((state) => state.period);
const mutateCharts = Charts.useMutation();
return (
<Flex justify="center" align="center" gap="2">
<Text>Player count</Text>
<Select.Root
value={period}
onValueChange={(period) => {
mutateCharts((draft) => {
draft.period = period as ChartsPeriod;
});
}}
>
<Select.Trigger />
<Select.Content>
<Select.Item value="total">Total</Select.Item>
<Select.Item value="d_120">Past 120 days</Select.Item>
<Select.Item value="d_90">Past 90 days</Select.Item>
<Select.Item value="d_60">Past 60 days</Select.Item>
<Select.Item value="d_30">This month</Select.Item>
<Select.Item value="d_7">This week</Select.Item>
<Select.Item value="d_1">Yesterday</Select.Item>
</Select.Content>
</Select.Root>
</Flex>
);
}
@@ -0,0 +1,99 @@
import {
TANK_CLASSES,
TIERS,
TIER_ROMAN_NUMERALS,
TankClass,
formatCompact,
} from '@blitzkit/core';
import strings from '@blitzkit/core/lang/en-US.json';
import { useStore } from '@nanostores/react';
import { Box, Flex, Heading } from '@radix-ui/themes';
import { awaitableAverageDefinitions } from '../../core/awaitables/averageDefinitions';
import { awaitableTankDefinitions } from '../../core/awaitables/tankDefinitions';
import { filterTank } from '../../core/blitzkit/filterTank';
import { useAveragesExclusionRatio } from '../../hooks/useAveragesExclusionRatio';
import { Charts } from '../../stores/charts';
import { $tankFilters } from '../../stores/tankFilters';
import { ThemedBar } from '../Nivo/ThemedBar';
const [tankDefinitions, averageDefinitions] = await Promise.all([
awaitableTankDefinitions,
awaitableAverageDefinitions,
]);
const averageDefinitionsArray = Object.entries(averageDefinitions.averages);
export function PlayersInPeriod() {
const period = Charts.use((state) => state.period);
const ratio = useAveragesExclusionRatio();
const filters = useStore($tankFilters);
const data = TIERS.map((tier) => {
const players: Record<TankClass, number> = {
0: 0,
1: 0,
2: 0,
3: 0,
};
averageDefinitionsArray.forEach(([idString, entry]) => {
const id = Number(idString);
const tank = tankDefinitions.tanks[id];
if (
tank === undefined ||
tank.tier !== tier ||
!filterTank(filters, tank)
) {
return;
}
players[tank.class] += entry.samples[period] * ratio;
});
const bar: Record<string, number | string> = {
tier: TIER_ROMAN_NUMERALS[tier],
};
TANK_CLASSES.forEach((c) => {
bar[strings.common.tank_class_short[c]] = players[c];
});
return bar;
});
return (
<Flex direction="column" gap="4">
<Heading align="center">Players by tier & class</Heading>
<Box height="25rem">
<ThemedBar
data={data}
keys={TANK_CLASSES.map((c) => strings.common.tank_class_short[c])}
indexBy="tier"
margin={{ right: 90, bottom: 50, left: 90 }}
axisBottom={{
legend: 'Tier',
legendPosition: 'middle',
legendOffset: 32,
}}
axisLeft={{
legend: 'Players',
legendPosition: 'middle',
legendOffset: -60,
format: formatCompact,
}}
legends={[
{
dataFrom: 'keys',
anchor: 'bottom-right',
direction: 'column',
translateX: 120,
itemWidth: 100,
itemHeight: 20,
},
]}
valueFormat={formatCompact}
/>
</Box>
</Flex>
);
}
@@ -1,100 +0,0 @@
import {
formatCompact,
TANK_CLASSES,
TankClass,
TIER_ROMAN_NUMERALS,
TIERS,
} from '@blitzkit/core';
import strings from '@blitzkit/core/lang/en-US.json';
import { useStore } from '@nanostores/react';
import { Box, Flex, Heading } from '@radix-ui/themes';
import { awaitableAverageDefinitions } from '../../../../core/awaitables/averageDefinitions';
import { awaitableTankDefinitions } from '../../../../core/awaitables/tankDefinitions';
import { filterTank } from '../../../../core/blitzkit/filterTank';
import { useAveragesExclusionRatio } from '../../../../hooks/useAveragesExclusionRatio';
import { $tankFilters } from '../../../../stores/tankFilters';
import { TankPerformanceEphemeral } from '../../../../stores/tankPerformanceEphemeral';
import { ThemedBar } from '../../../Nivo/ThemedBar';
const [tankDefinitions, averageDefinitions] = await Promise.all([
awaitableTankDefinitions,
awaitableAverageDefinitions,
]);
const averageDefinitionsArray = Object.entries(averageDefinitions.averages);
export function PlayersByTierAndClass() {
const playerCountPeriod = TankPerformanceEphemeral.use(
(state) => state.playerCountPeriod,
);
const ratio = useAveragesExclusionRatio();
const filters = useStore($tankFilters);
return (
<Flex direction="column" gap="4">
<Heading align="center">Players by tier & class</Heading>
<Box height="25rem">
<ThemedBar
data={TIERS.map((tier) => {
const players: Record<TankClass, number> = {
0: 0,
1: 0,
2: 0,
3: 0,
};
averageDefinitionsArray.forEach(([idString, entry]) => {
const id = Number(idString);
const tank = tankDefinitions.tanks[id];
if (
tank === undefined ||
tank.tier !== tier ||
!filterTank(filters, tank)
) {
return;
}
players[tank.class] += entry.samples[playerCountPeriod] * ratio;
});
const bar: Record<string, number | string> = {
tier: TIER_ROMAN_NUMERALS[tier],
};
TANK_CLASSES.forEach((c) => {
bar[strings.common.tank_class_short[c]] = players[c];
});
return bar;
})}
keys={TANK_CLASSES.map((c) => strings.common.tank_class_short[c])}
indexBy="tier"
margin={{ right: 90, bottom: 50, left: 90 }}
axisBottom={{
legend: 'Tier',
legendPosition: 'middle',
legendOffset: 32,
}}
axisLeft={{
legend: 'Players',
legendPosition: 'middle',
legendOffset: -60,
format: formatCompact,
}}
legends={[
{
dataFrom: 'keys',
anchor: 'bottom-right',
direction: 'column',
translateX: 120,
itemWidth: 100,
itemHeight: 20,
},
]}
valueFormat={formatCompact}
/>
</Box>
</Flex>
);
}
@@ -1,12 +0,0 @@
import { Flex } from '@radix-ui/themes';
import { PlayersByTierAndClass } from './components/PlayersByTierAndClass';
export function Charts() {
return (
<Flex justify="center">
<Flex direction="column" maxWidth="45rem" flexGrow="1" py="6" gap="9">
<PlayersByTierAndClass />
</Flex>
</Flex>
);
}
@@ -1,31 +0,0 @@
import { Tabs } from '@radix-ui/themes';
import {
TankPerformanceEphemeral,
TankPerformanceMode,
} from '../../stores/tankPerformanceEphemeral';
export function ModeSwitcher() {
const mode = TankPerformanceEphemeral.use((state) => state.mode);
const mutateTankPerformanceMode = TankPerformanceEphemeral.useMutation();
return (
<Tabs.Root
value={`${mode}`}
onValueChange={(value) => {
mutateTankPerformanceMode((draft) => {
draft.mode = Number(value) as TankPerformanceMode;
});
}}
mb="5"
>
<Tabs.List justify="center">
<Tabs.Trigger value={`${TankPerformanceMode.Table}`}>
Table
</Tabs.Trigger>
<Tabs.Trigger value={`${TankPerformanceMode.Charts}`}>
Charts
</Tabs.Trigger>
</Tabs.List>
</Tabs.Root>
);
}
+4 -3
View File
@@ -179,14 +179,14 @@ export const tierListTool: Tool = {
export const chartsTool: Tool = {
id: 'charts',
title: 'Charts',
description: 'Lots of Blitz charts',
description: 'Lots of graphs',
pageDescription:
'Charts and graphs on anything and everything about tanks, players, and events in World of Tanks Blitz',
button: {
text: 'Nerd out',
color: 'yellow',
color: 'bronze',
},
image: '9s68NwR',
image: '6fhkljA',
branches: ['main', 'dev', 'opentest', 'preview'],
};
@@ -194,6 +194,7 @@ export const TOOLS: Tool[] = [
tankopediaTool,
compareTool,
performanceTool,
chartsTool,
galleryTool,
sessionTool,
tierListTool,
@@ -0,0 +1,29 @@
import { Flex } from '@radix-ui/themes';
import { ChartsControl } from '../../../components/Charts/ChartsControl';
import { PlayersInPeriod } from '../../../components/Charts/PlayersInPeriod';
import { PageWrapper } from '../../../components/PageWrapper';
import { FilterControl } from '../../../components/TankSearch/components/FilterControl';
import { Charts } from '../../../stores/charts';
export function Page() {
return (
<Charts.Provider>
<Content />
</Charts.Provider>
);
}
function Content() {
return (
<PageWrapper maxWidth="unset">
<FilterControl />
<ChartsControl />
<Flex justify="center">
<Flex direction="column" maxWidth="45rem" flexGrow="1" py="6" gap="9">
<PlayersInPeriod />
</Flex>
</Flex>
</PageWrapper>
);
}
@@ -0,0 +1,9 @@
---
import { chartsTool } from '../../../constants/tools';
import ToolLayout from '../../../layouts/ToolLayout.astro';
import { Page } from './_index';
---
<ToolLayout tool={chartsTool}>
<Page client:load />
</ToolLayout>
@@ -1,12 +1,9 @@
import { PageWrapper } from '../../../components/PageWrapper';
import { Charts } from '../../../components/Performance/Charts';
import { PerformanceInfo } from '../../../components/Performance/PerformanceInfo';
import { PlayerCountControl } from '../../../components/Performance/PlayerCountControl';
import { TankTable } from '../../../components/Performance/Table';
import { FilterControl } from '../../../components/TankSearch/components/FilterControl';
import {
TankPerformanceEphemeral,
TankPerformanceMode,
} from '../../../stores/tankPerformanceEphemeral';
import { TankPerformanceEphemeral } from '../../../stores/tankPerformanceEphemeral';
import { TankPerformanceSort } from '../../../stores/tankPerformanceSort';
import type { MaybeSkeletonComponentProps } from '../../../types/maybeSkeletonComponentProps';
@@ -21,18 +18,13 @@ export function Page({ skeleton }: MaybeSkeletonComponentProps) {
}
function Content({ skeleton }: MaybeSkeletonComponentProps) {
const mode = TankPerformanceEphemeral.use((state) => state.mode);
return (
<PageWrapper color="jade" maxWidth="100%">
{/* <ModeSwitcher /> */}
{/* <PerformanceInfo skeleton={skeleton} /> */}
<PerformanceInfo skeleton={skeleton} />
<FilterControl />
<PlayerCountControl />
{mode === TankPerformanceMode.Table && <TankTable skeleton={skeleton} />}
{mode === TankPerformanceMode.Charts && <Charts />}
<TankTable skeleton={skeleton} />
</PageWrapper>
);
}
+15
View File
@@ -0,0 +1,15 @@
import type { Samples } from '@blitzkit/core';
import { create } from 'zustand';
import { createContextualStore } from '../core/zustand/createContextualStore';
export interface Charts {
period: ChartsPeriod;
}
export type ChartsPeriod = keyof Samples;
export const Charts = createContextualStore(() =>
create<Charts>()(() => ({
period: 'd_30',
})),
);
@@ -4,12 +4,6 @@ import { createContextualStore } from '../core/zustand/createContextualStore';
export interface TankPerformanceEphemeral {
playerCountPeriod: PlayerCountPeriod;
mode: TankPerformanceMode;
}
export enum TankPerformanceMode {
Table,
Charts,
}
export type PlayerCountPeriod = keyof Samples;
@@ -17,6 +11,5 @@ export type PlayerCountPeriod = keyof Samples;
export const TankPerformanceEphemeral = createContextualStore(() =>
create<TankPerformanceEphemeral>()(() => ({
playerCountPeriod: 'd_30',
mode: TankPerformanceMode.Table,
})),
);