Compare commits
5 Commits
35c0003159
...
5adc05e1bb
| Author | SHA1 | Date | |
|---|---|---|---|
| 5adc05e1bb | |||
| f049805124 | |||
| e4be7c4830 | |||
| f0950d3241 | |||
| 454b77e778 |
60
src/lib/chart.ts
Normal file
60
src/lib/chart.ts
Normal 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,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -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;
|
||||||
|
};
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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),
|
||||||
|
|||||||
@ -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),
|
||||||
|
|||||||
@ -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),
|
||||||
|
|||||||
@ -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),
|
||||||
|
|||||||
@ -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">
|
<LineChart data={points_chart_data} options={points_chart_options} />
|
||||||
<!-- Podium -->
|
</div>
|
||||||
<!-- <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="mt-2">
|
||||||
<div class="card w-full bg-surface-100 p-2 shadow">
|
{#await data.racepickpointstotal then racepickpointstotal}
|
||||||
<LineChart data={points_chart_data} options={points_chart_options} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-2">
|
|
||||||
<Table data={racepickpointstotal} columns={leaderboard_columns} />
|
<Table data={racepickpointstotal} columns={leaderboard_columns} />
|
||||||
</div>
|
{/await}
|
||||||
{/await}
|
</div>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
23
src/routes/statistics/+page.ts
Normal file
23
src/routes/statistics/+page.ts
Normal 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),
|
||||||
|
};
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user