Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 60 additions & 4 deletions src/components/player/PitchSelectionChart.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { usePlayerPitchSelection } from "@/hooks/api/Player";
import { Chart as ChartJS, ArcElement, Tooltip, Legend, ChartOptions, LegendItem, Chart } from 'chart.js';
import { Chart as ChartJS, ArcElement, Tooltip as ChartTooltip, Legend, ChartOptions, LegendItem, Chart } from 'chart.js';
import { Doughnut } from 'react-chartjs-2';
import { useSettings } from "../Settings";
import { LoadingMini } from "../Loading";
import { Player } from "@/types/Player";
import { pitchAbbrToName, Player, pitchAbbrToCategory } from "@/types/Player";
import { Tooltip } from "../ui/Tooltip";

ChartJS.register(ArcElement, Tooltip, Legend);
ChartJS.register(ArcElement, ChartTooltip, Legend);
ChartJS.defaults.font.family = 'GeistSans, "GeistSans Fallback"';

const pitchTypeColors: Record<string, string> = {
Expand Down Expand Up @@ -39,7 +40,7 @@ export function PitchUsageChart({ id }: { id: string }) {
}

// Expected pitch selection stats from Player object
export function PitchSelectionChart({ player }: { player: Player}) {
export function PitchSelectionChart({ player }: { player: Player }) {

const { settings } = useSettings();

Expand Down Expand Up @@ -132,3 +133,58 @@ export function PitchChart({ data, settings, pitchSelection, title = "Pitch Sele
</div>
);
}
// table to show pitch type bonuses and category bonuses
export function PitchBonusesTable({ player }: { player: Player }) {
const pitchCategoryBonuses = player.pitch_category_bonuses;
const pitchTypeBonuses = player.pitch_type_bonuses;
const pitchTypes = player.pitch_selection ? Object.keys(player.pitch_selection) : [];
if (!pitchCategoryBonuses && !pitchTypeBonuses) {
return <div />;
}

return (
<div className="mt-6 w-full max-w-2xl">
<div className="text-lg font-bold mb-4">Pitch Bonuses</div>
<table className="w-full table-auto border-collapse border border-theme-text">
<thead>
<tr>
<th className="border border-theme-text px-4 py-2 text-left">Pitch Type</th>
<th className="border border-theme-text px-4 py-2 text-left">Type Bonus</th>
<th className="border border-theme-text px-4 py-2 text-left">
<Tooltip content={
<div>
{pitchCategoryBonuses && Object.entries(pitchCategoryBonuses).map(([category, bonus]) => (
<div key={category}>{category}: {bonus > 0 ? `+${(bonus * 100).toFixed(1)}%` : `${(bonus * 100).toFixed(1)}%`}</div>
))}
</div>
}>
<span className="cursor-help">Category Bonus</span>
</Tooltip>
</th>
<th className="border border-theme-text px-4 py-2 text-left">Total Bonus</th>
</tr>
</thead>
<tbody>
{Object.entries(pitchAbbrToName)
.filter(([_abbr, name]) => pitchTypes.includes(name))
.map(([abbr, name]) => {
const typeBonus = pitchTypeBonuses?.[abbr] ?? 0;
const category = pitchAbbrToCategory[abbr];
const categoryBonus = pitchCategoryBonuses?.[category] ?? 0;
const totalBonus = typeBonus + categoryBonus;

return (
<tr key={abbr}>
<td className="border border-theme-text px-4 py-2">{name}</td>
<td className="border border-theme-text px-4 py-2">{typeBonus > 0 ? `+${(typeBonus * 100).toFixed(1)}%` : `${(typeBonus * 100).toFixed(1)}%`}</td>
<td className="border border-theme-text px-4 py-2">{categoryBonus > 0 ? `+${(categoryBonus * 100).toFixed(1)}%` : `${(categoryBonus * 100).toFixed(1)}%`}</td>
<td className="border border-theme-text px-4 py-2">{totalBonus > 0 ? `+${(totalBonus * 100).toFixed(1)}%` : `${(totalBonus * 100).toFixed(1)}%`}</td>
</tr>
);
})}
</tbody>
</table>
</div>
);

}
3 changes: 2 additions & 1 deletion src/components/player/PlayerPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Player } from "@/types/Player";
import { usePlayer } from "@/hooks/api/Player";
import { useTeam } from "@/hooks/api/Team";
import PlayerAttributes from "./PlayerAttributes";
import { PitchSelectionChart, PitchUsageChart } from "./PitchSelectionChart";
import { PitchBonusesTable, PitchSelectionChart, PitchUsageChart } from "./PitchSelectionChart";
import Link from "next/link";
import { PlayerPageHeader } from "./PlayerPageHeader";
import PlayerStatsTables from "./PlayerStatsTables";
Expand Down Expand Up @@ -144,6 +144,7 @@ export function PlayerPage({ id }: PlayerPageProps) {
? <PitchUsageChart id={id} />
: <div>Batter charts coming soon!</div>}
<PitchSelectionChart player={player} />
<PitchBonusesTable player={player} />
</>

)}
Expand Down
19 changes: 18 additions & 1 deletion src/types/Player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,7 @@ export function mapBoon(raw: any): Boon | undefined {
}
}

const pitchAbbrToName: Record<string, string> = {
export const pitchAbbrToName: Record<string, string> = {
'FF': 'Fastball',
'SI': 'Sinker',
'FC': 'Cutter',
Expand All @@ -752,6 +752,23 @@ const pitchAbbrToName: Record<string, string> = {
'ST': 'Sweeper',
}

// fast, offspeed, breaking
export const pitchAbbrToCategory: Record<string, string> = {
'FF': 'Fast',
'SI': 'Fast',
'FC': 'Fast',
'SL': 'Breaking',
'CU': 'Breaking',
'KC': 'Breaking',
'CH': 'Offspeed',
'FS': 'Offspeed',
'ST': 'Breaking',
}

export function mapPitchTypeAbbrToName(abbr: string): string {
return pitchAbbrToName[abbr] || abbr;
}

function mapPitchSelection(raw: any): Record<string, number> {
if (!raw || !raw.PitchSelection || !raw.PitchTypes) {
return {};
Expand Down