From a738dc4a22d927cb3f38e7c1b6846fce33009efd Mon Sep 17 00:00:00 2001 From: calli Date: Tue, 22 Apr 2025 17:47:06 +0300 Subject: [PATCH] add planet extraction simulation to tooltip --- .../ExtractionSimulationTooltip.tsx | 174 ++++++++++++++++++ .../PlanetaryInteraction/PlanetTableRow.tsx | 78 ++++++-- src/app/context/Context.tsx | 3 +- src/app/page.tsx | 2 +- src/types.ts | 8 +- 5 files changed, 247 insertions(+), 18 deletions(-) create mode 100644 src/app/components/PlanetaryInteraction/ExtractionSimulationTooltip.tsx diff --git a/src/app/components/PlanetaryInteraction/ExtractionSimulationTooltip.tsx b/src/app/components/PlanetaryInteraction/ExtractionSimulationTooltip.tsx new file mode 100644 index 0000000..766ce0a --- /dev/null +++ b/src/app/components/PlanetaryInteraction/ExtractionSimulationTooltip.tsx @@ -0,0 +1,174 @@ +import React from 'react'; +import { Box, Paper, Typography, Stack } from '@mui/material'; +import { Line } from 'react-chartjs-2'; +import { getProgramOutputPrediction } from './ExtractionSimulation'; +import { PI_TYPES_MAP } from '@/const'; +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + PointElement, + LineElement, + Title, + Tooltip, + Legend +} from 'chart.js'; +import { DateTime } from 'luxon'; +import Countdown from 'react-countdown'; + +ChartJS.register( + CategoryScale, + LinearScale, + PointElement, + LineElement, + Title, + Tooltip, + Legend +); + +interface ExtractorConfig { + typeId: number; + baseValue: number; + cycleTime: number; + installTime: string; + expiryTime: string; +} + +interface ExtractionSimulationTooltipProps { + extractors: ExtractorConfig[]; +} + +export const ExtractionSimulationTooltip: React.FC = ({ + extractors +}) => { + const CYCLE_TIME = 30 * 60; // 30 minutes in seconds + + // Calculate program duration and cycles for each extractor + const extractorPrograms = extractors.map(extractor => { + const installDate = new Date(extractor.installTime); + const expiryDate = new Date(extractor.expiryTime); + const programDuration = (expiryDate.getTime() - installDate.getTime()) / 1000; // Convert to seconds + return { + ...extractor, + programDuration, + cycles: Math.floor(programDuration / CYCLE_TIME) + }; + }); + + const maxCycles = Math.max(...extractorPrograms.map(e => e.cycles)); + + // Get output predictions for each extractor + const extractorOutputs = extractorPrograms.map(extractor => ({ + typeId: extractor.typeId, + cycleTime: CYCLE_TIME, + cycles: extractor.cycles, + prediction: getProgramOutputPrediction( + extractor.baseValue, + CYCLE_TIME, + extractor.cycles + ) + })); + + // Create datasets for the chart + const datasets = extractorOutputs.map((output, index) => { + const hue = (360 / extractors.length) * index; + return { + label: `${PI_TYPES_MAP[output.typeId]?.name ?? `Resource ${output.typeId}`}`, + data: output.prediction, + borderColor: `hsl(${hue}, 70%, 50%)`, + backgroundColor: `hsl(${hue}, 70%, 80%)`, + tension: 0.4 + }; + }); + + const chartData = { + labels: Array.from({ length: maxCycles }, (_, i) => { + return (i % 4 === 0) ? `Cycle ${i + 1}` : ''; + }), + datasets + }; + + const chartOptions = { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + position: 'top' as const, + }, + title: { + display: true, + text: 'Extraction Output Prediction' + }, + tooltip: { + callbacks: { + title: (context: any) => `Cycle ${context[0].dataIndex + 1}`, + label: (context: any) => `Output: ${context.raw.toFixed(1)} units` + } + } + }, + scales: { + y: { + beginAtZero: true, + title: { + display: true, + text: 'Units per Cycle' + } + }, + x: { + ticks: { + autoSkip: true, + maxTicksLimit: 24 + } + } + } + }; + + return ( + + + +
+ +
+
+ + + {extractorPrograms.map(({ typeId, cycleTime, cycles, installTime, expiryTime }) => { + const prediction = getProgramOutputPrediction( + extractors.find(e => e.typeId === typeId)?.baseValue || 0, + CYCLE_TIME, + cycles + ); + const totalOutput = prediction.reduce((sum, val) => sum + val, 0); + + return ( + + + {PI_TYPES_MAP[typeId]?.name} + + + + • Total Output: {totalOutput.toFixed(1)} units per program + + + • Cycle Time: {(cycleTime / 60).toFixed(1)} minutes + + + • Program Cycles: {cycles} + + + • Average per Cycle: {(totalOutput / cycles).toFixed(1)} units + + + • Expires in: + + + + ); + })} + + +
+
+ ); +}; \ No newline at end of file diff --git a/src/app/components/PlanetaryInteraction/PlanetTableRow.tsx b/src/app/components/PlanetaryInteraction/PlanetTableRow.tsx index 1d97dd9..35d6402 100644 --- a/src/app/components/PlanetaryInteraction/PlanetTableRow.tsx +++ b/src/app/components/PlanetaryInteraction/PlanetTableRow.tsx @@ -20,6 +20,7 @@ import { PlanetConfigDialog } from "../PlanetConfig/PlanetConfigDialog"; import PinsCanvas3D from "./PinsCanvas3D"; import { alertModeVisibility, timeColor } from "./timeColors"; import { ExtractionSimulationDisplay } from './ExtractionSimulationDisplay'; +import { ExtractionSimulationTooltip } from './ExtractionSimulationTooltip'; import { ProductionNode } from './ExtractionSimulation'; import { Collapse, Box, Stack } from "@mui/material"; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; @@ -160,7 +161,38 @@ export const PlanetTableRow = ({ height={theme.custom.cardImageSize / 6} style={{ marginRight: "5px" }} /> - {planetInfoUniverse?.name} + e.extractor_details?.product_type_id && e.extractor_details?.qty_per_cycle) + .map(e => ({ + typeId: e.extractor_details!.product_type_id!, + baseValue: e.extractor_details!.qty_per_cycle!, + cycleTime: e.extractor_details!.cycle_time || 3600, + installTime: e.install_time ?? "", + expiryTime: e.expiry_time ?? "" + }))} + /> + } + componentsProps={{ + tooltip: { + sx: { + bgcolor: 'background.paper', + '& .MuiTooltip-arrow': { + color: 'background.paper', + }, + maxWidth: 'none', + width: 'fit-content' + } + } + }} + > + + {planetInfoUniverse?.name} + + @@ -383,18 +415,38 @@ export const PlanetTableRow = ({ - e.extractor_details?.product_type_id && e.extractor_details?.qty_per_cycle) - .map(e => ({ - typeId: e.extractor_details!.product_type_id!, - baseValue: e.extractor_details!.qty_per_cycle!, - cycleTime: e.extractor_details!.cycle_time || 3600, - installTime: e.install_time ?? "", - expiryTime: e.expiry_time ?? "" - }))} - productionNodes={productionNodes} - /> + e.extractor_details?.product_type_id && e.extractor_details?.qty_per_cycle) + .map(e => ({ + typeId: e.extractor_details!.product_type_id!, + baseValue: e.extractor_details!.qty_per_cycle!, + cycleTime: e.extractor_details!.cycle_time || 3600, + installTime: e.install_time ?? "", + expiryTime: e.expiry_time ?? "" + }))} + /> + } + componentsProps={{ + tooltip: { + sx: { + bgcolor: 'background.paper', + '& .MuiTooltip-arrow': { + color: 'background.paper', + }, + } + } + }} + > +
+ + {planetInfoUniverse?.name} + +
+
diff --git a/src/app/context/Context.tsx b/src/app/context/Context.tsx index f48ba77..eeecac8 100644 --- a/src/app/context/Context.tsx +++ b/src/app/context/Context.tsx @@ -1,7 +1,6 @@ import { EvePraisalResult } from "@/eve-praisal"; -import { AccessToken, CharacterUpdate } from "@/types"; +import { AccessToken, CharacterUpdate, PlanetConfig } from "@/types"; import { Dispatch, SetStateAction, createContext } from "react"; -import { PlanetConfig } from "../components/PlanetConfig/PlanetConfigDialog"; export const CharacterContext = createContext<{ characters: AccessToken[]; diff --git a/src/app/page.tsx b/src/app/page.tsx index a0d339c..0da784d 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -17,7 +17,7 @@ import { import { useSearchParams } from "next/navigation"; import { EvePraisalResult, fetchAllPrices } from "@/eve-praisal"; import { getPlanet, getPlanetUniverse, getPlanets } from "@/planets"; -import { PlanetConfig } from "./components/PlanetConfig/PlanetConfigDialog"; +import { PlanetConfig } from "@/types"; const Home = () => { const searchParams = useSearchParams(); diff --git a/src/types.ts b/src/types.ts index b2ad6e8..c2e6aad 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,3 @@ -import { PlanetConfig } from "./app/components/PlanetConfig/PlanetConfigDialog"; - export interface AccessToken { access_token: string; expires_at: number; @@ -119,3 +117,9 @@ export interface Pin { amount: number; }>; } + +export interface PlanetConfig { + characterId: number; + planetId: number; + excludeFromTotals: boolean; +}