From 6695e3ae6ad985c4df61914f70285b3151a9d204 Mon Sep 17 00:00:00 2001 From: Jeremy Scheff Date: Tue, 15 Oct 2024 16:10:07 -0400 Subject: [PATCH] Popover --- TODO | 6 +- src/ui/components/BoxScoreWrapper.tsx | 250 +++++++++++++++++--------- src/ui/components/PlusMinus.tsx | 6 +- 3 files changed, 169 insertions(+), 93 deletions(-) diff --git a/TODO b/TODO index e925ec0b8..6e84227f6 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,9 @@ 4 factors Net Pts metric from Basketball Beyond Paper https://x.com/basketball_gm/status/1846231902417478073 -- show total on hover - - test mobile +- pointer on hover of table - if possible, use estimates from current league for lgEffic and lgOrbPct + - after N games? + - prior to N games, estimate from league settings + - always estimate? test - blog - sometimes they can point in opposite directions http://localhost/l/132/game_log/CLE_5/2024/242 - orb-drb: 15-31 and 16-29 diff --git a/src/ui/components/BoxScoreWrapper.tsx b/src/ui/components/BoxScoreWrapper.tsx index 75e0d4e71..dd6cf0ed8 100644 --- a/src/ui/components/BoxScoreWrapper.tsx +++ b/src/ui/components/BoxScoreWrapper.tsx @@ -17,7 +17,7 @@ import { import BoxScore from "./BoxScore"; import { range } from "../../common/utils"; import getWinner from "../../common/getWinner"; -import PlusMinus from "./PlusMinus"; +import { OverlayTrigger, Popover } from "react-bootstrap"; const TeamNameLink = ({ children, @@ -301,6 +301,46 @@ const getFourFactorsNetPoints = (teams: any[]) => { return fourFactorsNetPoints; }; +const FourFactorsAmountLine = ({ + amount, + teams, +}: { + amount: number; + teams: any[]; +}) => { + const abbrev = amount > 0 ? teams[0].abbrev : teams[1].abbrev; + return ( + <> + +{Math.abs(amount).toFixed(1)} for {abbrev} + + ); +}; + +const FourFactorsTotalLine = ({ + fourFactorsNetPoints, + teams, +}: { + fourFactorsNetPoints: ReturnType; + teams: any[]; +}) => { + const total = + fourFactorsNetPoints.efg + + fourFactorsNetPoints.tov + + fourFactorsNetPoints.orb + + fourFactorsNetPoints.ft; + const totalFixed = Math.abs(total).toFixed(1); + if (totalFixed === "0.0") { + return "even"; + } + + const abbrev = total > 0 ? teams[0].abbrev : teams[1].abbrev; + return ( + <> + +{totalFixed} for {abbrev} + + ); +}; + const FourFactors = ({ teams }: { teams: any[] }) => { const fourFactorsNetPoints = getFourFactorsNetPoints(teams); @@ -313,92 +353,128 @@ const FourFactors = ({ teams }: { teams: any[] }) => { const gradientStyle = gradientStyleFactory(-maxMagnitude, 0, 0, maxMagnitude); return ( - - - - - - - - - - - - {teams.map((t, i) => { - const t2 = teams[1 - i]; - - const efg = (100 * (t.fg + t.tp / 2)) / t.fga; - const tovp = (100 * t.tov) / (t.fga + 0.44 * t.fta + t.tov); - const orbp = (100 * t.orb) / (t.orb + t2.drb); - const ftpFga = t.ft / t.fga; - - const efg2 = (100 * (t2.fg + t2.tp / 2)) / t2.fga; - const tovp2 = (100 * t2.tov) / (t2.fga + 0.44 * t2.fta + t2.tov); - const orbp2 = (100 * t2.orb) / (t2.orb + t.drb); - const ftpFga2 = t2.ft / t2.fga; - - // Can't always take Math.abs because sometimes the 4 factors leader is not the net pts leader, since the 4 factors values are not used directly in the net pts formulas - const gradientSign = i === 0 ? 1 : -1; - - return ( - - - - - - - ); - })} - - - - - - - -
eFG%TOV%ORB% - FT/FGA -
efg2 - ? gradientStyle(gradientSign * fourFactorsNetPoints.efg) - : undefined - } - > - {helpers.roundStat(efg, "efg")} - - {helpers.roundStat(tovp, "tovp")} - orbp2 - ? gradientStyle(gradientSign * fourFactorsNetPoints.orb) - : undefined - } - > - {helpers.roundStat(orbp, "orbp")} - ftpFga2 - ? gradientStyle(gradientSign * fourFactorsNetPoints.ft) - : undefined - } - > - {helpers.roundStat(ftpFga, "ftpFga")} -
- {fourFactorsNetPoints.efg} - - {fourFactorsNetPoints.tov} - - {fourFactorsNetPoints.orb} - - {fourFactorsNetPoints.ft} -
+ + +

Estimated value of four factors in net points:

+
    +
  • + eFG%:{" "} + +
  • +
  • + TOV%:{" "} + +
  • +
  • + ORB%:{" "} + +
  • +
  • + FT/FGA:{" "} + +
  • +
  • + Total:{" "} + +
  • +
+
+ + } + placement="bottom" + rootClose + trigger="click" + > + + + + + + + + + + + + {teams.map((t, i) => { + const t2 = teams[1 - i]; + + const efg = (100 * (t.fg + t.tp / 2)) / t.fga; + const tovp = (100 * t.tov) / (t.fga + 0.44 * t.fta + t.tov); + const orbp = (100 * t.orb) / (t.orb + t2.drb); + const ftpFga = t.ft / t.fga; + + const efg2 = (100 * (t2.fg + t2.tp / 2)) / t2.fga; + const tovp2 = (100 * t2.tov) / (t2.fga + 0.44 * t2.fta + t2.tov); + const orbp2 = (100 * t2.orb) / (t2.orb + t.drb); + const ftpFga2 = t2.ft / t2.fga; + + // Can't always take Math.abs because sometimes the 4 factors leader is not the net pts leader, since the 4 factors values are not used directly in the net pts formulas + const gradientSign = i === 0 ? 1 : -1; + + return ( + + + + + + + ); + })} + +
eFG%TOV%ORB% + FT/FGA +
efg2 + ? gradientStyle(gradientSign * fourFactorsNetPoints.efg) + : undefined + } + > + {helpers.roundStat(efg, "efg")} + + {helpers.roundStat(tovp, "tovp")} + orbp2 + ? gradientStyle(gradientSign * fourFactorsNetPoints.orb) + : undefined + } + > + {helpers.roundStat(orbp, "orbp")} + ftpFga2 + ? gradientStyle(gradientSign * fourFactorsNetPoints.ft) + : undefined + } + > + {helpers.roundStat(ftpFga, "ftpFga")} +
+
); }; diff --git a/src/ui/components/PlusMinus.tsx b/src/ui/components/PlusMinus.tsx index a676c8b4b..c4e67bc4f 100644 --- a/src/ui/components/PlusMinus.tsx +++ b/src/ui/components/PlusMinus.tsx @@ -1,10 +1,8 @@ const PlusMinus = ({ children, - color = true, decimalPlaces = 1, }: { children: number | null | undefined; - color?: boolean; decimalPlaces?: number; }) => { if (children == undefined) { @@ -21,9 +19,9 @@ const PlusMinus = ({ {children !== 0 ? ( 0 + : children > 0 ? "text-success" : undefined }