feat: graphs

This commit is contained in:
TrèsAbhi committed 2024-10-21 22:42:11 -05:00
1 parent 973d24a615
commit dee0dc6a14
14 files changed
+377 -17

No files matched your search

+1
View File
@@ -63,6 +63,7 @@
"DVPLCRC",
"dvpm",
"dxgi",
"echarts",
"Everytime",
"FASTNAME",
"FOURCC",
BIN
View File
Binary file not shown.
@@ -55,7 +55,18 @@ export const TANK_ICONS_COLLECTOR: Record<TankClass, string> = {
[TankClass.MEDIUM]: 'https://i.imgur.com/u8YDMBh.png',
[TankClass.HEAVY]: 'https://i.imgur.com/8xRf3nc.png',
};
export const TIERS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] as const;
export const TIERS = [
Tier.I,
Tier.II,
Tier.III,
Tier.IV,
Tier.V,
Tier.VI,
Tier.VII,
Tier.VIII,
Tier.IX,
Tier.X,
];
export const TIER_ROMAN_NUMERALS: Record<Tier, string> = {
[Tier.I]: 'I',
[Tier.II]: 'II',
+1 -1
View File
@@ -13,7 +13,6 @@
"@blitzkit/core": "workspace:*",
"@nanostores/persistent": "^0.10.2",
"@nanostores/react": "^0.7.3",
"@nivo/core": "^0.87.0",
"@radix-ui/colors": "^3.0.0",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/themes": "^3.1.3",
@@ -22,6 +21,7 @@
"@vanilla-extract/css": "^1.15.5",
"@vanilla-extract/dynamic": "^2.1.2",
"compare-versions": "^6.1.1",
"echarts": "^5.5.1",
"immer": "^10.1.1",
"js-cookie": "^3.0.5",
"markdown-to-jsx": "^7.5.0",
@@ -0,0 +1,64 @@
import { Box, Flex, Heading, type BoxProps } from '@radix-ui/themes';
import type { ECharts, EChartsOption, SetOptionOpts } from 'echarts';
import { getInstanceByDom, init } from 'echarts';
import type { CSSProperties } from 'react';
import { useEffect, useRef } from 'react';
import { echartsTheme } from './theme';
export type EChartProps = BoxProps & {
option: EChartsOption;
style?: CSSProperties;
settings?: SetOptionOpts;
};
/**
* Thank you Maneet Goyal! Heavily modified.
*
* https://dev.to/manufac/using-apache-echarts-with-react-and-typescript-353k
*/
export function EChart({
option,
settings,
title,
...props
}: EChartProps): JSX.Element {
const chartRef = useRef<HTMLDivElement>(null);
useEffect(() => {
// Initialize chart
let chart: ECharts | undefined;
if (chartRef.current !== null) {
chart = init(chartRef.current, echartsTheme, { renderer: 'svg' });
}
// Add chart resize listener
// ResizeObserver is leading to a bit janky UX
function resizeChart() {
chart?.resize();
}
window.addEventListener('resize', resizeChart);
// Return cleanup function
return () => {
chart?.dispose();
window.removeEventListener('resize', resizeChart);
};
}, []);
useEffect(() => {
if (chartRef.current === null) return undefined;
const chart = getInstanceByDom(chartRef.current);
if (chart === undefined) return undefined;
chart.setOption(option, settings);
}, [option, settings]);
return (
<Flex direction="column" align="center">
{title && <Heading align="center">{title}</Heading>}
<Box {...props} ref={chartRef} width="100%" />
</Flex>
);
}
@@ -0,0 +1,140 @@
import { Var } from '../../core/radix/var';
export const echartsTheme = {
seriesCnt: '3',
backgroundColor: Var('color-background'),
titleColor: '#ff0000',
subtitleColor: '#dddddd',
textColorShow: false,
textColor: '#333',
markTextColor: '#293441',
color: [
Var('accent-indicator'),
'#fc97af',
'#87f7cf',
'#f7f494',
'#72ccff',
'#f7c5a0',
'#d4a4eb',
'#d2f5a6',
'#76f2f2',
],
borderColor: '#ccc',
borderWidth: 0,
visualMapColor: ['#fc97af', '#87f7cf'],
legendTextColor: '#999999',
kColor: '#fc97af',
kColor0: 'transparent',
kBorderColor: '#fc97af',
kBorderColor0: '#87f7cf',
kBorderWidth: '2',
lineWidth: '3',
symbolSize: '0',
symbol: 'circle',
symbolBorderWidth: '4',
lineSmooth: true,
graphLineWidth: '1',
graphLineColor: '#ffffff',
mapLabelColor: '#893448',
mapLabelColorE: 'rgb(137,52,72)',
mapBorderColor: '#999999',
mapBorderColorE: '#eb8146',
mapBorderWidth: 0.5,
mapBorderWidthE: 1,
mapAreaColor: '#f3f3f3',
mapAreaColorE: 'rgba(255,178,72,1)',
axes: [
{
type: 'all',
name: '通用坐标轴',
axisLineShow: true,
axisLineColor: '#666666',
axisTickShow: false,
axisTickColor: '#333',
axisLabelShow: true,
axisLabelColor: '#aaaaaa',
splitLineShow: false,
splitLineColor: ['#e6e6e6'],
splitAreaShow: false,
splitAreaColor: ['rgba(250,250,250,0.05)', 'rgba(200,200,200,0.02)'],
},
{
type: 'category',
name: '类目坐标轴',
axisLineShow: true,
axisLineColor: '#333',
axisTickShow: true,
axisTickColor: '#333',
axisLabelShow: true,
axisLabelColor: '#333',
splitLineShow: false,
splitLineColor: ['#ccc'],
splitAreaShow: false,
splitAreaColor: ['rgba(250,250,250,0.3)', 'rgba(200,200,200,0.3)'],
},
{
type: 'value',
name: '数值坐标轴',
axisLineShow: true,
axisLineColor: '#333',
axisTickShow: true,
axisTickColor: '#333',
axisLabelShow: true,
axisLabelColor: '#333',
splitLineShow: true,
splitLineColor: ['#ccc'],
splitAreaShow: false,
splitAreaColor: ['rgba(250,250,250,0.3)', 'rgba(200,200,200,0.3)'],
},
{
type: 'log',
name: '对数坐标轴',
axisLineShow: true,
axisLineColor: '#333',
axisTickShow: true,
axisTickColor: '#333',
axisLabelShow: true,
axisLabelColor: '#333',
splitLineShow: true,
splitLineColor: ['#ccc'],
splitAreaShow: false,
splitAreaColor: ['rgba(250,250,250,0.3)', 'rgba(200,200,200,0.3)'],
},
{
type: 'time',
name: '时间坐标轴',
axisLineShow: true,
axisLineColor: '#333',
axisTickShow: true,
axisTickColor: '#333',
axisLabelShow: true,
axisLabelColor: '#333',
splitLineShow: true,
splitLineColor: ['#ccc'],
splitAreaShow: false,
splitAreaColor: ['rgba(250,250,250,0.3)', 'rgba(200,200,200,0.3)'],
},
],
axisSeperateSetting: false,
toolboxColor: '#999999',
toolboxEmphasisColor: '#666666',
tooltipAxisColor: '#cccccc',
tooltipAxisWidth: 1,
timelineLineColor: '#87f7cf',
timelineLineWidth: 1,
timelineItemColor: '#87f7cf',
timelineItemColorE: '#f7f494',
timelineCheckColor: '#fc97af',
timelineCheckBorderColor: '#fc97af',
timelineItemBorderWidth: 1,
timelineControlColor: '#87f7cf',
timelineControlBorderColor: '#87f7cf',
timelineControlBorderWidth: 0.5,
timelineLabelColor: '#87f7cf',
datazoomBackgroundColor: 'rgba(255,255,255,0)',
datazoomDataColor: 'rgba(114,204,255,1)',
datazoomFillColor: 'rgba(114,204,255,0.2)',
datazoomHandleColor: '#72ccff',
datazoomHandleWidth: '100',
datazoomLabelColor: '#333333',
};
@@ -1,3 +0,0 @@
export function Charts() {
return 'amogos';
}
@@ -0,0 +1,57 @@
import {
TIERS,
TIER_ROMAN_NUMERALS,
fetchAverageDefinitions,
fetchTankDefinitions,
} from '@blitzkit/core';
import { TankPerformanceEphemeral } from '../../../../stores/tankPerformanceEphemeral';
import { EChart } from '../../../EChart';
const averageDefinitions = await fetchAverageDefinitions();
const tankDefinitions = await fetchTankDefinitions();
const averageDefinitionsArray = Object.entries(averageDefinitions.averages);
export function BattleDistributionAcrossTiers() {
const playerCountPeriod = TankPerformanceEphemeral.use(
(state) => state.playerCountPeriod,
);
return (
<EChart
height="25rem"
title="Battle distribution across tiers"
option={{
xAxis: {
type: 'category',
data: TIERS.map((tier) => TIER_ROMAN_NUMERALS[tier]),
},
yAxis: {
type: 'value',
},
series: [
{
data: TIERS.map((tier) => {
return averageDefinitionsArray.reduce(
(accumulator, [idString, entry]) => {
const id = Number(idString);
const tank = tankDefinitions.tanks[id];
if (tank === undefined || tank.tier !== tier) {
return accumulator;
}
return (
accumulator +
entry.mu.battles * entry.samples[playerCountPeriod]
);
},
0,
);
}),
type: 'bar',
},
],
}}
/>
);
}
@@ -0,0 +1,65 @@
import {
TANK_CLASSES,
TIERS,
TIER_ROMAN_NUMERALS,
fetchAverageDefinitions,
fetchTankDefinitions,
} from '@blitzkit/core';
import strings from '@blitzkit/core/lang/en-US.json';
import { TankPerformanceEphemeral } from '../../../../stores/tankPerformanceEphemeral';
import { EChart } from '../../../EChart';
const averageDefinitions = await fetchAverageDefinitions();
const tankDefinitions = await fetchTankDefinitions();
const averageDefinitionsArray = Object.entries(averageDefinitions.averages);
export function BattleDistributionAcrossTiersByClass() {
const playerCountPeriod = TankPerformanceEphemeral.use(
(state) => state.playerCountPeriod,
);
return (
<EChart
height="25rem"
title="Battle distribution across tiers by class"
option={{
xAxis: {
type: 'category',
data: TIERS.map((tier) => TIER_ROMAN_NUMERALS[tier]),
},
yAxis: {
type: 'value',
},
legend: {
data: TANK_CLASSES.map((c) => strings.common.tank_class_short[c]),
},
series: TANK_CLASSES.map((c) => ({
name: strings.common.tank_class_short[c],
data: TIERS.map((tier) => {
return averageDefinitionsArray.reduce(
(accumulator, [idString, entry]) => {
const id = Number(idString);
const tank = tankDefinitions.tanks[id];
if (
tank === undefined ||
tank.tier !== tier ||
tank.class !== c
) {
return accumulator;
}
return (
accumulator +
entry.mu.battles * entry.samples[playerCountPeriod]
);
},
0,
);
}),
type: 'bar',
})),
}}
/>
);
}
@@ -0,0 +1,14 @@
import { Flex } from '@radix-ui/themes';
import { BattleDistributionAcrossTiers } from './components/BattleDistributionAcrossTiers';
import { BattleDistributionAcrossTiersByClass } from './components/BattleDistributionAcrossTiersByClass';
export function Charts() {
return (
<Flex justify="center">
<Flex direction="column" maxWidth="35rem" flexGrow="1" py="6" gap="9">
<BattleDistributionAcrossTiers />
<BattleDistributionAcrossTiersByClass />
</Flex>
</Flex>
);
}
@@ -6,9 +6,9 @@ export function StickyTableRoot({ style, ...props }: RootProps) {
const table = useRef<HTMLTableElement>(null);
useEffect(() => {
// table.current
// ?.querySelector('table')
// ?.style.setProperty('overflow', 'auto');
table.current
?.querySelector('table')
?.style.setProperty('overflow', 'auto');
}, []);
return (
+17 -1
View File
@@ -8,7 +8,23 @@ type Name =
| `color-${'background' | `panel-${'solid' | 'translucent'}` | 'surface' | 'overlay'}`
| `radius-${1 | 2 | 3 | 4 | 5 | 6 | 'full'}`
| `space-${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}`
| `${RadixColor}-${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12}`;
| `${RadixColor | 'accent' | 'gray'}-${
| 1
| 2
| 3
| 4
| 5
| 6
| 7
| 8
| 9
| 10
| 11
| 12
| 'surface'
| 'indicator'
| 'track'
| 'contrast'}`;
export function Var(name: Name) {
return `var(--${name})`;
@@ -31,14 +31,9 @@ function Content({ skeleton }: MaybeSkeletonComponentProps) {
<PerformanceInfo skeleton={skeleton} />
<FilterControl />
<PlayerCountControl />
{mode === TankPerformanceMode.Table && (
<>
<PlayerCountControl />
<TankTable skeleton={skeleton} />
</>
)}
{mode === TankPerformanceMode.Table && <TankTable skeleton={skeleton} />}
{mode === TankPerformanceMode.Charts && <Charts />}
</PageWrapper>
);
@@ -23,6 +23,6 @@ export enum PlayerCountPeriod {
export const TankPerformanceEphemeral = createContextualStore(() =>
create<TankPerformanceEphemeral>()(() => ({
playerCountPeriod: PlayerCountPeriod.ThisMonth,
mode: TankPerformanceMode.Table,
mode: TankPerformanceMode.Charts,
})),
);