Compare commits

...

5 Commits

10 changed files with 233 additions and 79 deletions

60
src/lib/chart.ts Normal file
View File

@ -0,0 +1,60 @@
import { type LineChartOptions, ScaleTypes } from "@carbon/charts-svelte";
export const make_chart_options = (
title: string,
bottom: string,
left: string,
group: string = "group",
width: string = "100%",
height: string = "400px",
): LineChartOptions => {
return {
title: title,
axes: {
bottom: {
mapsTo: bottom,
scaleType: ScaleTypes.LABELS,
},
left: {
mapsTo: left,
scaleType: ScaleTypes.LINEAR,
},
},
data: {
groupMapsTo: group,
},
curve: "curveMonotoneX",
// toolbar: {
// enabled: false,
// },
animations: true,
// canvasZoom: {
// enabled: false,
// },
grid: {
x: {
enabled: true,
alignWithAxisTicks: true,
},
y: {
enabled: true,
alignWithAxisTicks: true,
},
},
legend: {
enabled: true,
clickable: true,
position: "top",
},
points: {
enabled: true,
radius: 5,
},
tooltip: {
showTotal: false,
},
resizable: true,
width: width,
height: height,
};
};

View File

@ -13,6 +13,7 @@ import type {
RaceResult, RaceResult,
ScrapedDriverStanding, ScrapedDriverStanding,
ScrapedRaceResult, ScrapedRaceResult,
ScrapedRaceResultAcc,
ScrapedStartingGrid, ScrapedStartingGrid,
ScrapedTeamStanding, ScrapedTeamStanding,
SeasonPick, SeasonPick,
@ -359,3 +360,16 @@ export const fetch_scraped_raceresults = async (
return scraped_raceresults; return scraped_raceresults;
}; };
/**
* Fetch all [ScrapedRaceResultsAcc] from the database, ordered ascendingly by race step.
*/
export const fetch_scraped_raceresultsacc = async (
fetch: (_: any) => Promise<Response>,
): Promise<ScrapedRaceResultAcc[]> => {
const scraped_raceresultsacc: ScrapedRaceResultAcc[] = await pb
.collection("scraped_raceresultsacc")
.getFullList({ fetch: fetch, sort: "+race_step" });
return scraped_raceresultsacc;
};

View File

@ -176,6 +176,13 @@ export interface ScrapedRaceResult {
points: number; points: number;
} }
export interface ScrapedRaceResultAcc {
id: string;
race_step: number; // This maps to races
driver_code: string; // This maps to drivers
acc_points: number;
}
export interface ScrapedDriverStanding { export interface ScrapedDriverStanding {
id: string; id: string;
driver_code: string; // This maps to drivers driver_code: string; // This maps to drivers

View File

@ -2,7 +2,7 @@ import { fetch_drivers, fetch_scraped_driverstandings } from "$lib/fetch";
import type { PageLoad } from "../../../$types"; import type { PageLoad } from "../../../$types";
export const load: PageLoad = async ({ fetch, depends }) => { export const load: PageLoad = async ({ fetch, depends }) => {
depends("data:scraped_driverstandings", "data:drivers"); depends("data:official", "data:drivers");
return { return {
scraped_driverstandings: fetch_scraped_driverstandings(fetch), scraped_driverstandings: fetch_scraped_driverstandings(fetch),

View File

@ -2,7 +2,7 @@ import { fetch_drivers, fetch_races, fetch_scraped_raceresults } from "$lib/fetc
import type { PageLoad } from "../../../$types"; import type { PageLoad } from "../../../$types";
export const load: PageLoad = async ({ fetch, depends }) => { export const load: PageLoad = async ({ fetch, depends }) => {
depends("data:scraped_raceresults", "data:races", "data:drivers"); depends("data:official", "data:races", "data:drivers");
return { return {
scraped_raceresults: fetch_scraped_raceresults(fetch), scraped_raceresults: fetch_scraped_raceresults(fetch),

View File

@ -2,7 +2,7 @@ import { fetch_drivers, fetch_races, fetch_scraped_startinggrids } from "$lib/fe
import type { PageLoad } from "../../../$types"; import type { PageLoad } from "../../../$types";
export const load: PageLoad = async ({ fetch, depends }) => { export const load: PageLoad = async ({ fetch, depends }) => {
depends("data:scraped_startinggrids", "data:races", "data:drivers"); depends("data:official", "data:races", "data:drivers");
return { return {
scraped_startinggrids: fetch_scraped_startinggrids(fetch), scraped_startinggrids: fetch_scraped_startinggrids(fetch),

View File

@ -2,7 +2,7 @@ import { fetch_scraped_teamstandings, fetch_teams } from "$lib/fetch";
import type { PageLoad } from "../../../$types"; import type { PageLoad } from "../../../$types";
export const load: PageLoad = async ({ fetch, depends }) => { export const load: PageLoad = async ({ fetch, depends }) => {
depends("data:scraped_teamstandings", "data:teams"); depends("data:official", "data:teams");
return { return {
scraped_teamstandings: fetch_scraped_teamstandings(fetch), scraped_teamstandings: fetch_scraped_teamstandings(fetch),

View File

@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import { make_chart_options } from "$lib/chart";
import { Table, type TableColumn } from "$lib/components"; import { Table, type TableColumn } from "$lib/components";
import { get_by_value } from "$lib/database"; import { get_by_value } from "$lib/database";
import type { RacePickPoints, RacePickPointsAcc, User } from "$lib/schema"; import type { RacePickPoints, RacePickPointsAcc, User } from "$lib/schema";
@ -28,11 +29,13 @@
data_value_name: "user", data_value_name: "user",
label: "User", label: "User",
valuefun: async (value: string): Promise<string> => valuefun: async (value: string): Promise<string> =>
get_by_value(await data.users, "id", value)?.firstname ?? "Invalid", `<span class='badge variant-filled-surface'>${get_by_value(await data.users, "id", value)?.firstname ?? "Invalid"}</span>`,
}, },
{ {
data_value_name: "total_points", data_value_name: "total_points",
label: "Total", label: "Total",
valuefun: async (value: string): Promise<string> =>
`<span class='badge variant-filled-surface'>${value}</span>`,
}, },
{ {
data_value_name: "total_pxx_points", data_value_name: "total_pxx_points",
@ -71,85 +74,23 @@
); );
}); });
const points_chart_options: LineChartOptions = { const points_chart_options: LineChartOptions = make_chart_options(
title: "I ❤️ CumSum", "I ❤️ CumSum",
axes: { "step",
bottom: { "points",
mapsTo: "step", );
scaleType: ScaleTypes.LABELS,
},
left: {
mapsTo: "points",
scaleType: ScaleTypes.LINEAR,
},
},
curve: "curveMonotoneX",
// toolbar: {
// enabled: false,
// },
animations: true,
// canvasZoom: {
// enabled: false,
// },
grid: {
x: {
enabled: true,
alignWithAxisTicks: true,
},
y: {
enabled: true,
alignWithAxisTicks: true,
},
},
legend: {
enabled: true,
clickable: true,
position: "top",
},
points: {
enabled: true,
radius: 5,
},
tooltip: {
showTotal: false,
},
resizable: true,
width: "100%",
height: "400px",
};
</script> </script>
<svelte:head> <svelte:head>
<title>Formula 11 - Leaderboard</title> <title>Formula 11 - Leaderboard</title>
</svelte:head> </svelte:head>
{#await Promise.all( [data.users, data.racepickpoints, data.racepickpointsacc, data.racepickpointstotal], ) then [users, racepickpoints, racepickpointsacc, racepickpointstotal]} <div class="card w-full bg-surface-100 p-2 shadow">
<div class="flex gap-2">
<!-- Podium -->
<!-- <div class="card w-60 bg-surface-100 p-2 shadow"> -->
<!-- <div class="flex h-20 w-full gap-1"></div> -->
<!-- <div class="flex h-20 w-full gap-1"> -->
<!-- <div class="w-20"> -->
<!-- <div class="h-[30%] w-full"></div> -->
<!-- <div class="h-[70%] w-full bg-surface-500"></div> -->
<!-- </div> -->
<!-- <div class="w-20"> -->
<!-- <div class="h-[100%] w-full bg-surface-500"></div> -->
<!-- </div> -->
<!-- <div class="w-20"> -->
<!-- <div class="h-[60%] w-full"></div> -->
<!-- <div class="h-[40%] w-full bg-surface-600"></div> -->
<!-- </div> -->
<!-- </div> -->
<!-- </div> -->
<!-- Points chart -->
<div class="card w-full bg-surface-100 p-2 shadow">
<LineChart data={points_chart_data} options={points_chart_options} /> <LineChart data={points_chart_data} options={points_chart_options} />
</div> </div>
</div>
<div class="mt-2"> <div class="mt-2">
{#await data.racepickpointstotal then racepickpointstotal}
<Table data={racepickpointstotal} columns={leaderboard_columns} /> <Table data={racepickpointstotal} columns={leaderboard_columns} />
</div> {/await}
{/await} </div>

View File

@ -1,3 +1,112 @@
<script lang="ts">
import { Table, type TableColumn } from "$lib/components";
import type { Driver, ScrapedRaceResultAcc } from "$lib/schema";
import {
LineChart,
ScaleTypes,
type ChartTabularData,
type LineChartOptions,
} from "@carbon/charts-svelte";
import "@carbon/charts-svelte/styles.css";
import type { PageData } from "./$types";
import { get_by_value } from "$lib/database";
import { make_chart_options } from "$lib/chart";
let { data }: { data: PageData } = $props();
// Await promises
let drivers: Driver[] | undefined = $state(undefined);
data.drivers.then((d: Driver[]) => (drivers = d));
let scraped_raceresultsacc: ScrapedRaceResultAcc[] | undefined = $state(undefined);
data.scraped_raceresultsacc.then((s: ScrapedRaceResultAcc[]) => (scraped_raceresultsacc = s));
const drivers_columns: TableColumn[] = $derived([
{
data_value_name: "position",
label: "Position",
valuefun: async (value: string): Promise<string> =>
`<span class='badge variant-filled-surface'>${value}</span>`,
},
{
data_value_name: "driver_code",
label: "Driver",
},
{
data_value_name: "points",
label: "Points",
valuefun: async (value: string): Promise<string> =>
`<span class='badge variant-filled-surface'>${value}</span>`,
},
]);
const teams_columns: TableColumn[] = $derived([
{
data_value_name: "position",
label: "Position",
valuefun: async (value: string): Promise<string> =>
`<span class='badge variant-filled-surface'>${value}</span>`,
},
{
data_value_name: "team_fullname",
label: "team",
},
{
data_value_name: "points",
label: "Points",
valuefun: async (value: string): Promise<string> =>
`<span class='badge variant-filled-surface'>${value}</span>`,
},
]);
const drivers_chart_data: ChartTabularData = $derived.by(() => {
if (!drivers || !scraped_raceresultsacc) return [];
const active_drivers: Driver[] = drivers.filter((driver: Driver) => driver.active);
const active_results: ScrapedRaceResultAcc[] = scraped_raceresultsacc.filter(
(result: ScrapedRaceResultAcc) => get_by_value(drivers, "code", result.driver_code)?.active,
);
return active_drivers
.map((driver: Driver) => {
return {
group: driver.code,
step: "0",
points: 0,
};
})
.concat(
active_results.map((result: ScrapedRaceResultAcc) => {
return {
group: result.driver_code,
step: result.race_step.toString(),
points: result.acc_points,
};
}),
);
});
const drivers_chart_options: LineChartOptions = make_chart_options("Drivers", "step", "points");
</script>
<svelte:head> <svelte:head>
<title>Formula 11 - Statistics</title> <title>Formula 11 - Statistics</title>
</svelte:head> </svelte:head>
<div class="card w-full bg-surface-100 p-2 shadow">
<LineChart data={drivers_chart_data} options={drivers_chart_options} />
</div>
<div class="mt-2 grid w-full grid-cols-1 gap-2 lg:grid-cols-2">
<div class="w-full">
{#await data.scraped_driverstandings then driverstandings}
<Table data={driverstandings} columns={drivers_columns} />
{/await}
</div>
<div class="w-full">
{#await data.scraped_teamstandings then teamstandings}
<Table data={teamstandings} columns={teams_columns} />
{/await}
</div>
</div>

View File

@ -0,0 +1,23 @@
import {
fetch_drivers,
fetch_scraped_driverstandings,
fetch_scraped_raceresults,
fetch_scraped_raceresultsacc,
fetch_scraped_startinggrids,
fetch_scraped_teamstandings,
} from "$lib/fetch";
import type { PageLoad } from "../$types";
export const load: PageLoad = async ({ fetch, depends }) => {
depends("data:drivers", "data:official");
return {
drivers: fetch_drivers(fetch),
scraped_driverstandings: fetch_scraped_driverstandings(fetch),
scraped_teamstandings: fetch_scraped_teamstandings(fetch),
scraped_startinggrids: fetch_scraped_startinggrids(fetch),
scraped_raceresults: fetch_scraped_raceresults(fetch),
scraped_raceresultsacc: fetch_scraped_raceresultsacc(fetch),
};
};