Add diagrams to stats page
All checks were successful
Build Formula10 Docker Image / build-docker (push) Successful in 16s
All checks were successful
Build Formula10 Docker Image / build-docker (push) Successful in 16s
This commit is contained in:
@ -40,14 +40,10 @@ import formula10.controller.error_controller
|
|||||||
# - For season guess calc there is missing: Fastest laps + sprint points + sprint DNFs (in race result)
|
# - For season guess calc there is missing: Fastest laps + sprint points + sprint DNFs (in race result)
|
||||||
|
|
||||||
# - Auto calculate season points (display season points?)
|
# - Auto calculate season points (display season points?)
|
||||||
# - Generate static diagram using chart.js + templating the js (funny yikes)
|
|
||||||
# - Interesting stats:
|
# - Interesting stats:
|
||||||
# - Which driver was voted most for dnf (top 5)?
|
# - Which driver was voted most for dnf (top 5)?
|
||||||
|
|
||||||
# General
|
# General
|
||||||
# - Decouple names from IDs + Fix Valtteri/Russel spelling errors
|
# - Decouple names from IDs + Fix Valtteri/Russel spelling errors
|
||||||
# - Unit testing (as much as possible, but especially points calculation)
|
# - Unit testing (as much as possible, but especially points calculation)
|
||||||
# - Add links to the official F1 stats page (for quali/result), probably best to store entire link in DB (because they are not entirely regular)?
|
# - Add links to the official F1 stats page (for quali/result), probably best to store entire link in DB (because they are not entirely regular)?
|
||||||
|
|
||||||
# Possible but probably not
|
|
||||||
# - Show cards of previous race results, like with season guesses?
|
|
@ -1,5 +1,5 @@
|
|||||||
import json
|
import json
|
||||||
from typing import Any, Callable, Dict, List, Tuple, overload
|
from typing import Any, Callable, Dict, List, overload
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from formula10.domain.domain_model import Model
|
from formula10.domain.domain_model import Model
|
||||||
@ -105,8 +105,8 @@ class PointsModel(Model):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
_points_per_step: Dict[str, List[int]] | None = None
|
_points_per_step: Dict[str, List[int]] | None = None
|
||||||
_wdc_points: Dict[str, int] | None = None
|
_driver_points_per_step: Dict[str, List[int]] | None = None
|
||||||
_wcc_points: Dict[str, int] | None = None
|
_team_points_per_step: Dict[str, List[int]] | None = None
|
||||||
_dnfs: Dict[str, int] | None = None
|
_dnfs: Dict[str, int] | None = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -134,32 +134,42 @@ class PointsModel(Model):
|
|||||||
return self._points_per_step
|
return self._points_per_step
|
||||||
|
|
||||||
# @todo Doesn't include fastest lap + sprint points
|
# @todo Doesn't include fastest lap + sprint points
|
||||||
def wdc_points(self) -> Dict[str, int]:
|
def driver_points_per_step(self) -> Dict[str, List[int]]:
|
||||||
if self._wdc_points is None:
|
"""
|
||||||
self._wdc_points = dict()
|
Returns a dictionary of lists, containing points per race for each driver.
|
||||||
|
"""
|
||||||
|
if self._driver_points_per_step is None:
|
||||||
|
self._driver_points_per_step = dict()
|
||||||
for driver in self.all_drivers(include_none=False):
|
for driver in self.all_drivers(include_none=False):
|
||||||
self._wdc_points[driver.name] = 0
|
self._driver_points_per_step[driver.name] = [0] * (len(self.all_races()) + 1) # Start at index 1, like the race numbers
|
||||||
|
|
||||||
for race_result in self.all_race_results():
|
for race_result in self.all_race_results():
|
||||||
for position, driver in race_result.standing.items():
|
for position, driver in race_result.standing.items():
|
||||||
self._wdc_points[driver.name] += DRIVER_RACE_POINTS[int(position)] if int(position) in DRIVER_RACE_POINTS else 0
|
driver_name: str = driver.name
|
||||||
|
race_number: int = race_result.race.number
|
||||||
|
|
||||||
|
self._driver_points_per_step[driver_name][race_number] = DRIVER_RACE_POINTS[int(position)] if int(position) in DRIVER_RACE_POINTS else 0
|
||||||
|
|
||||||
return self._wdc_points
|
return self._driver_points_per_step
|
||||||
|
|
||||||
def wcc_points(self) -> Dict[str, int]:
|
|
||||||
if self._wcc_points is None:
|
|
||||||
self._wcc_points = dict()
|
|
||||||
|
|
||||||
|
# @todo Doesn't include fastest lap + sprint points
|
||||||
|
def team_points_per_step(self) -> Dict[str, List[int]]:
|
||||||
|
"""
|
||||||
|
Returns a dictionary of lists, containing points per race for each team.
|
||||||
|
"""
|
||||||
|
if self._team_points_per_step is None:
|
||||||
|
self._team_points_per_step = dict()
|
||||||
for team in self.all_teams(include_none=False):
|
for team in self.all_teams(include_none=False):
|
||||||
self._wcc_points[team.name] = 0
|
self._team_points_per_step[team.name] = [0] * (len(self.all_races()) + 1) # Start at index 1, like the race numbers
|
||||||
|
|
||||||
for race_result in self.all_race_results():
|
for race_result in self.all_race_results():
|
||||||
for driver in race_result.standing.values():
|
for position, driver in race_result.standing.items():
|
||||||
self._wcc_points[driver.team.name] += self.wdc_points()[driver.name]
|
team_name: str = driver.team.name
|
||||||
|
race_number: int = race_result.race.number
|
||||||
|
|
||||||
return self._wcc_points
|
self._team_points_per_step[team_name][race_number] += DRIVER_RACE_POINTS[int(position)] if int(position) in DRIVER_RACE_POINTS else 0
|
||||||
|
|
||||||
|
return self._team_points_per_step
|
||||||
|
|
||||||
# @todo Doesn't include sprint dnfs
|
# @todo Doesn't include sprint dnfs
|
||||||
def dnfs(self) -> Dict[str, int]:
|
def dnfs(self) -> Dict[str, int]:
|
||||||
@ -175,38 +185,79 @@ class PointsModel(Model):
|
|||||||
|
|
||||||
return self._dnfs
|
return self._dnfs
|
||||||
|
|
||||||
def wdc_diff_2023(self) -> Dict[str, int]:
|
#
|
||||||
diff: Dict[str, int] = dict()
|
# Driver stats
|
||||||
|
#
|
||||||
|
|
||||||
for driver in self.all_drivers(include_none=False):
|
def driver_points_per_step_cumulative(self) -> Dict[str, List[int]]:
|
||||||
diff[driver.name] = WDC_STANDING_2023[driver.name] - self.wdc_standing_by_driver()[driver.name]
|
"""
|
||||||
|
Returns a dictionary of lists, containing cumulative points per race for each driver.
|
||||||
|
"""
|
||||||
|
points_per_step_cumulative: Dict[str, List[int]] = dict()
|
||||||
|
for driver_name, points in self.driver_points_per_step().items():
|
||||||
|
points_per_step_cumulative[driver_name] = np.cumsum(points).tolist()
|
||||||
|
|
||||||
return diff
|
return points_per_step_cumulative
|
||||||
|
|
||||||
def wcc_diff_2023(self) -> Dict[str, int]:
|
@overload
|
||||||
diff: Dict[str, int] = dict()
|
def driver_points_by(self, *, driver_name: str) -> List[int]:
|
||||||
|
"""
|
||||||
|
Returns a list of points per race for a specific driver.
|
||||||
|
"""
|
||||||
|
return self.driver_points_by(driver_name=driver_name)
|
||||||
|
|
||||||
for team in self.all_teams(include_none=False):
|
@overload
|
||||||
diff[team.name] = WCC_STANDING_2023[team.name] - self.wcc_standing_by_team()[team.name]
|
def driver_points_by(self, *, race_name: str) -> Dict[str, int]:
|
||||||
|
"""
|
||||||
|
Returns a dictionary of points per driver for a specific race.
|
||||||
|
"""
|
||||||
|
return self.driver_points_by(race_name=race_name)
|
||||||
|
|
||||||
return diff
|
@overload
|
||||||
|
def driver_points_by(self, *, driver_name: str, race_name: str) -> int:
|
||||||
|
"""
|
||||||
|
Returns the points for a specific race for a specific driver.
|
||||||
|
"""
|
||||||
|
return self.driver_points_by(driver_name=driver_name, race_name=race_name)
|
||||||
|
|
||||||
|
def driver_points_by(self, *, driver_name: str | None = None, race_name: str | None = None) -> List[int] | Dict[str, int] | int:
|
||||||
|
if driver_name is not None and race_name is None:
|
||||||
|
return self.driver_points_per_step()[driver_name]
|
||||||
|
|
||||||
|
if driver_name is None and race_name is not None:
|
||||||
|
race_number: int = self.race_by(race_name=race_name).number
|
||||||
|
points_by_race: Dict[str, int] = dict()
|
||||||
|
|
||||||
|
for _driver_name, points in self.driver_points_per_step().items():
|
||||||
|
points_by_race[_driver_name] = points[race_number]
|
||||||
|
|
||||||
|
return points_by_race
|
||||||
|
|
||||||
|
if driver_name is not None and race_name is not None:
|
||||||
|
race_number: int = self.race_by(race_name=race_name).number
|
||||||
|
|
||||||
|
return self.driver_points_per_step()[driver_name][race_number]
|
||||||
|
|
||||||
|
raise Exception("driver_points_by received an illegal combination of arguments")
|
||||||
|
|
||||||
|
def total_driver_points_by(self, driver_name: str) -> int:
|
||||||
|
return sum(self.driver_points_by(driver_name=driver_name))
|
||||||
|
|
||||||
def wdc_standing_by_position(self) -> Dict[int, List[str]]:
|
def wdc_standing_by_position(self) -> Dict[int, List[str]]:
|
||||||
standing: Dict[int, List[str]] = dict()
|
standing: Dict[int, List[str]] = dict()
|
||||||
|
|
||||||
for position in range(1, 21):
|
for position in range(1, len(self.all_drivers(include_none=False)) + 1):
|
||||||
standing[position] = list()
|
standing[position] = list()
|
||||||
|
|
||||||
position: int = 1
|
position: int = 1
|
||||||
last_points: int = 0
|
last_points: int = 0
|
||||||
|
|
||||||
comparator: Callable[[Tuple[str, int]], int] = lambda item: item[1]
|
for driver in self.all_drivers(include_none=False):
|
||||||
for driver_name, points in sorted(self.wdc_points().items(), key=comparator, reverse=True):
|
points: int = self.total_driver_points_by(driver.name)
|
||||||
if points < last_points:
|
if points < last_points:
|
||||||
position += 1
|
position += 1
|
||||||
|
|
||||||
standing[position].append(driver_name)
|
standing[position].append(driver.name)
|
||||||
|
|
||||||
last_points = points
|
last_points = points
|
||||||
|
|
||||||
return standing
|
return standing
|
||||||
@ -217,53 +268,18 @@ class PointsModel(Model):
|
|||||||
position: int = 1
|
position: int = 1
|
||||||
last_points: int = 0
|
last_points: int = 0
|
||||||
|
|
||||||
comparator: Callable[[Tuple[str, int]], int] = lambda item: item[1]
|
for driver in self.all_drivers(include_none=False):
|
||||||
for driver_name, points in sorted(self.wdc_points().items(), key=comparator, reverse=True):
|
points: int = self.total_driver_points_by(driver.name)
|
||||||
if points < last_points:
|
if points < last_points:
|
||||||
position += 1
|
position += 1
|
||||||
|
|
||||||
standing[driver_name] = position
|
standing[driver.name] = position
|
||||||
|
|
||||||
last_points = points
|
last_points = points
|
||||||
|
|
||||||
return standing
|
return standing
|
||||||
|
|
||||||
def wcc_standing_by_position(self) -> Dict[int, List[str]]:
|
def wdc_diff_2023_by(self, driver_name: str) -> int:
|
||||||
standing: Dict[int, List[str]] = dict()
|
return WDC_STANDING_2023[driver_name] - self.wdc_standing_by_driver()[driver_name]
|
||||||
|
|
||||||
for position in range (1, 11):
|
|
||||||
standing[position] = list()
|
|
||||||
|
|
||||||
position: int = 1
|
|
||||||
last_points: int = 0
|
|
||||||
|
|
||||||
comparator: Callable[[Tuple[str, int]], int] = lambda item: item[1]
|
|
||||||
for team_name, points in sorted(self.wcc_points().items(), key=comparator, reverse=True):
|
|
||||||
if points < last_points:
|
|
||||||
position += 1
|
|
||||||
|
|
||||||
standing[position].append(team_name)
|
|
||||||
|
|
||||||
last_points = points
|
|
||||||
|
|
||||||
return standing
|
|
||||||
|
|
||||||
def wcc_standing_by_team(self) -> Dict[str, int]:
|
|
||||||
standing: Dict[str, int] = dict()
|
|
||||||
|
|
||||||
position: int = 1
|
|
||||||
last_points: int = 0
|
|
||||||
|
|
||||||
comparator: Callable[[Tuple[str, int]], int] = lambda item: item[1]
|
|
||||||
for team_name, points in sorted(self.wcc_points().items(), key=comparator, reverse=True):
|
|
||||||
if points < last_points:
|
|
||||||
position += 1
|
|
||||||
|
|
||||||
standing[team_name] = position
|
|
||||||
|
|
||||||
last_points = points
|
|
||||||
|
|
||||||
return standing
|
|
||||||
|
|
||||||
def most_dnf_names(self) -> List[str]:
|
def most_dnf_names(self) -> List[str]:
|
||||||
dnf_names: List[str] = list()
|
dnf_names: List[str] = list()
|
||||||
@ -284,13 +300,13 @@ class PointsModel(Model):
|
|||||||
most_gained: int = 0
|
most_gained: int = 0
|
||||||
|
|
||||||
for driver in self.all_drivers(include_none=False):
|
for driver in self.all_drivers(include_none=False):
|
||||||
gained: int = self.wdc_diff_2023()[driver.name]
|
gained: int = self.wdc_diff_2023_by(driver.name)
|
||||||
|
|
||||||
if gained > most_gained:
|
if gained > most_gained:
|
||||||
most_gained = gained
|
most_gained = gained
|
||||||
|
|
||||||
for driver in self.all_drivers(include_none=False):
|
for driver in self.all_drivers(include_none=False):
|
||||||
gained: int = self.wdc_diff_2023()[driver.name]
|
gained: int = self.wdc_diff_2023_by(driver.name)
|
||||||
|
|
||||||
if gained == most_gained:
|
if gained == most_gained:
|
||||||
most_gained_names.append(driver.name)
|
most_gained_names.append(driver.name)
|
||||||
@ -302,13 +318,13 @@ class PointsModel(Model):
|
|||||||
most_lost: int = 100
|
most_lost: int = 100
|
||||||
|
|
||||||
for driver in self.all_drivers(include_none=False):
|
for driver in self.all_drivers(include_none=False):
|
||||||
lost: int = self.wdc_diff_2023()[driver.name]
|
lost: int = self.wdc_diff_2023_by(driver.name)
|
||||||
|
|
||||||
if lost < most_lost:
|
if lost < most_lost:
|
||||||
most_lost = lost
|
most_lost = lost
|
||||||
|
|
||||||
for driver in self.all_drivers(include_none=False):
|
for driver in self.all_drivers(include_none=False):
|
||||||
lost: int = self.wdc_diff_2023()[driver.name]
|
lost: int = self.wdc_diff_2023_by(driver.name)
|
||||||
|
|
||||||
if lost == most_lost:
|
if lost == most_lost:
|
||||||
most_lost_names.append(driver.name)
|
most_lost_names.append(driver.name)
|
||||||
@ -319,10 +335,70 @@ class PointsModel(Model):
|
|||||||
comparator: Callable[[Driver], int] = lambda driver: self.wdc_standing_by_driver()[driver.name]
|
comparator: Callable[[Driver], int] = lambda driver: self.wdc_standing_by_driver()[driver.name]
|
||||||
return sorted(self.all_drivers(include_none=False), key=comparator)
|
return sorted(self.all_drivers(include_none=False), key=comparator)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Team points
|
||||||
|
#
|
||||||
|
|
||||||
|
def team_points_per_step_cumulative(self) -> Dict[str, List[int]]:
|
||||||
|
"""
|
||||||
|
Returns a dictionary of lists, containing cumulative points per race for each team.
|
||||||
|
"""
|
||||||
|
points_per_step_cumulative: Dict[str, List[int]] = dict()
|
||||||
|
for team_name, points in self.team_points_per_step().items():
|
||||||
|
points_per_step_cumulative[team_name] = np.cumsum(points).tolist()
|
||||||
|
|
||||||
|
return points_per_step_cumulative
|
||||||
|
|
||||||
|
def total_team_points_by(self, team_name: str) -> int:
|
||||||
|
teammates: List[Driver] = self.drivers_by(team_name=team_name)
|
||||||
|
return sum(self.driver_points_by(driver_name=teammates[0].name)) + sum(self.driver_points_by(driver_name=teammates[1].name))
|
||||||
|
|
||||||
|
def wcc_standing_by_position(self) -> Dict[int, List[str]]:
|
||||||
|
standing: Dict[int, List[str]] = dict()
|
||||||
|
|
||||||
|
for position in range (1, len(self.all_teams(include_none=False)) + 1):
|
||||||
|
standing[position] = list()
|
||||||
|
|
||||||
|
position: int = 1
|
||||||
|
last_points: int = 0
|
||||||
|
|
||||||
|
for team in self.all_teams(include_none=False):
|
||||||
|
points: int = self.total_team_points_by(team.name)
|
||||||
|
if points < last_points:
|
||||||
|
position += 1
|
||||||
|
|
||||||
|
standing[position].append(team.name)
|
||||||
|
last_points = points
|
||||||
|
|
||||||
|
return standing
|
||||||
|
|
||||||
|
def wcc_standing_by_team(self) -> Dict[str, int]:
|
||||||
|
standing: Dict[str, int] = dict()
|
||||||
|
|
||||||
|
position: int = 1
|
||||||
|
last_points: int = 0
|
||||||
|
|
||||||
|
for team in self.all_teams(include_none=False):
|
||||||
|
points: int = self.total_team_points_by(team.name)
|
||||||
|
if points < last_points:
|
||||||
|
position += 1
|
||||||
|
|
||||||
|
standing[team.name] = position
|
||||||
|
last_points = points
|
||||||
|
|
||||||
|
return standing
|
||||||
|
|
||||||
|
def wcc_diff_2023_by(self, team_name: str) -> int:
|
||||||
|
return WCC_STANDING_2023[team_name] - self.wcc_standing_by_team()[team_name]
|
||||||
|
|
||||||
def teams_sorted_by_points(self) -> List[Team]:
|
def teams_sorted_by_points(self) -> List[Team]:
|
||||||
comparator: Callable[[Team], int] = lambda team: self.wcc_standing_by_team()[team.name]
|
comparator: Callable[[Team], int] = lambda team: self.wcc_standing_by_team()[team.name]
|
||||||
return sorted(self.all_teams(include_none=False), key=comparator)
|
return sorted(self.all_teams(include_none=False), key=comparator)
|
||||||
|
|
||||||
|
#
|
||||||
|
# User stats
|
||||||
|
#
|
||||||
|
|
||||||
def points_per_step_cumulative(self) -> Dict[str, List[int]]:
|
def points_per_step_cumulative(self) -> Dict[str, List[int]]:
|
||||||
"""
|
"""
|
||||||
Returns a dictionary of lists, containing cumulative points per race for each user.
|
Returns a dictionary of lists, containing cumulative points per race for each user.
|
||||||
@ -510,3 +586,39 @@ class PointsModel(Model):
|
|||||||
]
|
]
|
||||||
|
|
||||||
return json.dumps(data)
|
return json.dumps(data)
|
||||||
|
|
||||||
|
def cumulative_driver_points_data(self) -> str:
|
||||||
|
data: Dict[Any, Any] = dict()
|
||||||
|
|
||||||
|
data["labels"] = [0] + [
|
||||||
|
race.name for race in sorted(self.all_races(), key=lambda race: race.number)
|
||||||
|
]
|
||||||
|
|
||||||
|
data["datasets"] = [
|
||||||
|
{
|
||||||
|
"data": self.driver_points_per_step_cumulative()[driver.name],
|
||||||
|
"label": driver.name,
|
||||||
|
"fill": False
|
||||||
|
}
|
||||||
|
for driver in self.all_drivers(include_none=False)
|
||||||
|
]
|
||||||
|
|
||||||
|
return json.dumps(data)
|
||||||
|
|
||||||
|
def cumulative_team_points_data(self) -> str:
|
||||||
|
data: Dict[Any, Any] = dict()
|
||||||
|
|
||||||
|
data["labels"] = [0] + [
|
||||||
|
race.name for race in sorted(self.all_races(), key=lambda race: race.number)
|
||||||
|
]
|
||||||
|
|
||||||
|
data["datasets"] = [
|
||||||
|
{
|
||||||
|
"data": self.team_points_per_step_cumulative()[team.name],
|
||||||
|
"label": team.name,
|
||||||
|
"fill": False
|
||||||
|
}
|
||||||
|
for team in self.all_teams(include_none=False)
|
||||||
|
]
|
||||||
|
|
||||||
|
return json.dumps(data)
|
||||||
|
@ -11,3 +11,17 @@
|
|||||||
grid-column-gap: 8px;
|
grid-column-gap: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-grid-2 {
|
||||||
|
grid-template-columns: repeat(1, minmax(325px, 1fr));
|
||||||
|
grid-row-gap: 0;
|
||||||
|
grid-column-gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (min-width: 950px) {
|
||||||
|
.card-grid-2 {
|
||||||
|
grid-template-columns: repeat(2, minmax(450px, 1fr));
|
||||||
|
grid-row-gap: 0;
|
||||||
|
grid-column-gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid card-grid">
|
<div class="grid card-grid-2">
|
||||||
|
|
||||||
<div class="card shadow-sm mb-2">
|
<div class="card shadow-sm mb-2">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
@ -42,9 +42,9 @@
|
|||||||
<tr class="{% if driver_standing == 1 %}table-danger{% endif %}">
|
<tr class="{% if driver_standing == 1 %}table-danger{% endif %}">
|
||||||
<td class="text-center text-nowrap">{{ driver_standing }}</td>
|
<td class="text-center text-nowrap">{{ driver_standing }}</td>
|
||||||
<td class="text-center text-nowrap">{{ driver.name }}</td>
|
<td class="text-center text-nowrap">{{ driver.name }}</td>
|
||||||
<td class="text-center text-nowrap">{{ points.wdc_points()[driver.name] }}</td>
|
<td class="text-center text-nowrap">{{ points.total_driver_points_by(driver.name) }}</td>
|
||||||
<td class="text-center text-nowrap">{{ points.dnfs()[driver.name] }}</td>
|
<td class="text-center text-nowrap">{{ points.dnfs()[driver.name] }}</td>
|
||||||
<td class="text-center text-nowrap">{{ "%+d" % points.wdc_diff_2023()[driver.name] }}</td>
|
<td class="text-center text-nowrap">{{ "%+d" % points.wdc_diff_2023_by(driver.name) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -76,8 +76,8 @@
|
|||||||
<tr class="{% if team_standing == 1 %}table-danger{% endif %}">
|
<tr class="{% if team_standing == 1 %}table-danger{% endif %}">
|
||||||
<td class="text-center text-nowrap">{{ team_standing }}</td>
|
<td class="text-center text-nowrap">{{ team_standing }}</td>
|
||||||
<td class="text-center text-nowrap">{{ team.name }}</td>
|
<td class="text-center text-nowrap">{{ team.name }}</td>
|
||||||
<td class="text-center text-nowrap">{{ points.wcc_points()[team.name] }}</td>
|
<td class="text-center text-nowrap">{{ points.total_team_points_by(team.name) }}</td>
|
||||||
<td class="text-center text-nowrap">{{ points.wcc_diff_2023()[team.name] }}</td>
|
<td class="text-center text-nowrap">{{ points.wcc_diff_2023_by(team.name) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -86,6 +86,82 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow-sm mb-2">
|
||||||
|
<div class="card-header">
|
||||||
|
Driver history
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="chart-container" style="width: 100%; height: 40vh;">
|
||||||
|
<canvas id="driver-line-chart"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function cumulative_driver_points(data) {
|
||||||
|
return new Chart(document.getElementById("driver-line-chart"), {
|
||||||
|
type: 'line',
|
||||||
|
data: data,
|
||||||
|
options: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'History'
|
||||||
|
},
|
||||||
|
{#tension: 0,#}
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
min: 0,
|
||||||
|
{#max: 100#}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cumulative_driver_points({{ points.cumulative_driver_points_data() | safe }})
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow-sm mb-2">
|
||||||
|
<div class="card-header">
|
||||||
|
Team history
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="chart-container" style="width: 100%; height: 40vh;">
|
||||||
|
<canvas id="team-line-chart"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function cumulative_team_points(data) {
|
||||||
|
return new Chart(document.getElementById("team-line-chart"), {
|
||||||
|
type: 'line',
|
||||||
|
data: data,
|
||||||
|
options: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'History'
|
||||||
|
},
|
||||||
|
{#tension: 0,#}
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
min: 0,
|
||||||
|
{#max: 100#}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cumulative_team_points({{ points.cumulative_team_points_data() | safe }})
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock body %}
|
{% endblock body %}
|
||||||
|
Reference in New Issue
Block a user