RubenvanGemeren commited on
Commit ·
2be0ad4
1
Parent(s): 88cd236
Updated ui with new graph
Browse files- app.py +75 -69
- templates/players.html +112 -36
app.py
CHANGED
|
@@ -16,107 +16,113 @@ except:
|
|
| 16 |
project = hopsworks.login()
|
| 17 |
fs = project.get_feature_store()
|
| 18 |
|
| 19 |
-
players = fs.get_feature_group("fpl_predictions")
|
| 20 |
|
| 21 |
-
player_df = players.read()
|
| 22 |
|
| 23 |
-
player_data = player_df.to_json(orient="records")
|
| 24 |
|
| 25 |
-
players = json.loads(player_data)
|
| 26 |
|
| 27 |
-
for player in players:
|
| 28 |
-
|
| 29 |
-
|
| 30 |
|
| 31 |
# Mock data (replace with actual database query or file read)
|
| 32 |
sample_players = [
|
| 33 |
{
|
| 34 |
-
"
|
| 35 |
-
"
|
| 36 |
-
"
|
| 37 |
-
"
|
| 38 |
-
"
|
| 39 |
-
"
|
| 40 |
-
{"gameweek": 21, "total_points":
|
| 41 |
-
{"gameweek": 20, "total_points":
|
| 42 |
-
{"gameweek": 19, "total_points":
|
| 43 |
-
{"gameweek": 18, "total_points": 5, "predicted_points":
|
| 44 |
-
{"gameweek": 17, "total_points": 11, "predicted_points":
|
| 45 |
],
|
| 46 |
-
"
|
| 47 |
},
|
| 48 |
{
|
| 49 |
-
"
|
| 50 |
-
"
|
| 51 |
-
"
|
| 52 |
-
"
|
| 53 |
-
"
|
| 54 |
-
"
|
| 55 |
-
{"gameweek": 21, "total_points":
|
| 56 |
-
{"gameweek": 20, "total_points":
|
| 57 |
-
{"gameweek": 19, "total_points":
|
| 58 |
-
{"gameweek": 18, "total_points":
|
| 59 |
-
{"gameweek": 17, "total_points": 14, "predicted_points":
|
| 60 |
],
|
| 61 |
-
"
|
| 62 |
},
|
| 63 |
{
|
| 64 |
-
"
|
| 65 |
-
"
|
| 66 |
-
"
|
| 67 |
-
"
|
| 68 |
-
"
|
| 69 |
-
"
|
| 70 |
-
{"gameweek": 21, "total_points":
|
| 71 |
-
{"gameweek": 20, "total_points":
|
| 72 |
-
{"gameweek": 19, "total_points":
|
| 73 |
-
{"gameweek": 18, "total_points":
|
| 74 |
-
{"gameweek": 17, "total_points":
|
| 75 |
],
|
| 76 |
-
"
|
| 77 |
},
|
| 78 |
{
|
| 79 |
-
"
|
| 80 |
-
"
|
| 81 |
-
"
|
| 82 |
-
"
|
| 83 |
-
"
|
| 84 |
-
"
|
| 85 |
-
{"gameweek": 21, "total_points":
|
| 86 |
-
{"gameweek": 20, "total_points":
|
| 87 |
-
{"gameweek": 19, "total_points":
|
| 88 |
-
{"gameweek": 18, "total_points":
|
| 89 |
-
{"gameweek": 17, "total_points":
|
| 90 |
],
|
| 91 |
-
"
|
| 92 |
},
|
| 93 |
{
|
| 94 |
-
"
|
| 95 |
-
"
|
| 96 |
-
"
|
| 97 |
-
"
|
| 98 |
-
"
|
| 99 |
-
"
|
| 100 |
-
{"gameweek": 21, "total_points":
|
| 101 |
-
{"gameweek": 20, "total_points":
|
| 102 |
-
{"gameweek": 19, "total_points":
|
| 103 |
-
{"gameweek": 18, "total_points":
|
| 104 |
-
{"gameweek": 17, "total_points":
|
| 105 |
],
|
| 106 |
-
"
|
| 107 |
},
|
| 108 |
]
|
| 109 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
|
| 111 |
@app.route("/test")
|
| 112 |
def test():
|
| 113 |
-
return
|
| 114 |
|
| 115 |
|
| 116 |
@app.route("/")
|
| 117 |
def index():
|
| 118 |
"""Render the main page."""
|
| 119 |
-
return render_template("players.html", players=
|
| 120 |
|
| 121 |
|
| 122 |
@app.route("/api/players", methods=["POST", "GET"])
|
|
|
|
| 16 |
project = hopsworks.login()
|
| 17 |
fs = project.get_feature_store()
|
| 18 |
|
| 19 |
+
# players = fs.get_feature_group("fpl_predictions")
|
| 20 |
|
| 21 |
+
# player_df = players.read()
|
| 22 |
|
| 23 |
+
# player_data = player_df.to_json(orient="records")
|
| 24 |
|
| 25 |
+
# players = json.loads(player_data)
|
| 26 |
|
| 27 |
+
# for player in players:
|
| 28 |
+
# if player["predicted_score"] != None:
|
| 29 |
+
# player["predicted_score"] = round(player["predicted_score"])
|
| 30 |
|
| 31 |
# Mock data (replace with actual database query or file read)
|
| 32 |
sample_players = [
|
| 33 |
{
|
| 34 |
+
"first_name": "Harry",
|
| 35 |
+
"second_name": "Kane",
|
| 36 |
+
"position": "Midfielder",
|
| 37 |
+
"team": "Tottenham",
|
| 38 |
+
"total_points": 100,
|
| 39 |
+
"latest_predictions": [
|
| 40 |
+
{"gameweek": 21, "total_points": 18, "predicted_points": 15},
|
| 41 |
+
{"gameweek": 20, "total_points": 12, "predicted_points": 8},
|
| 42 |
+
{"gameweek": 19, "total_points": 9, "predicted_points": 5},
|
| 43 |
+
{"gameweek": 18, "total_points": 5, "predicted_points": 7},
|
| 44 |
+
{"gameweek": 17, "total_points": 11, "predicted_points": 7},
|
| 45 |
],
|
| 46 |
+
"predicted_score": 7,
|
| 47 |
},
|
| 48 |
{
|
| 49 |
+
"first_name": "Harry2",
|
| 50 |
+
"second_name": "Kane2",
|
| 51 |
+
"position": "Midfielder",
|
| 52 |
+
"team": "Tottenham",
|
| 53 |
+
"total_points": 100,
|
| 54 |
+
"latest_predictions": [
|
| 55 |
+
{"gameweek": 21, "total_points": 10, "predicted_points": 8},
|
| 56 |
+
{"gameweek": 20, "total_points": 5, "predicted_points": 8},
|
| 57 |
+
{"gameweek": 19, "total_points": 13, "predicted_points": 15},
|
| 58 |
+
{"gameweek": 18, "total_points": 5, "predicted_points": 7},
|
| 59 |
+
{"gameweek": 17, "total_points": 14, "predicted_points": 18},
|
| 60 |
],
|
| 61 |
+
"predicted_score": 7,
|
| 62 |
},
|
| 63 |
{
|
| 64 |
+
"first_name": "Harry3",
|
| 65 |
+
"second_name": "Kane3",
|
| 66 |
+
"position": "Midfielder",
|
| 67 |
+
"team": "Tottenham",
|
| 68 |
+
"total_points": 100,
|
| 69 |
+
"latest_predictions": [
|
| 70 |
+
{"gameweek": 21, "total_points": 14, "predicted_points": 11},
|
| 71 |
+
{"gameweek": 20, "total_points": 8, "predicted_points": 4},
|
| 72 |
+
{"gameweek": 19, "total_points": 11, "predicted_points": 7},
|
| 73 |
+
{"gameweek": 18, "total_points": 6, "predicted_points": 8},
|
| 74 |
+
{"gameweek": 17, "total_points": 12, "predicted_points": 9},
|
| 75 |
],
|
| 76 |
+
"predicted_score": 7,
|
| 77 |
},
|
| 78 |
{
|
| 79 |
+
"first_name": "Harry4",
|
| 80 |
+
"second_name": "Kane4",
|
| 81 |
+
"position": "Midfielder",
|
| 82 |
+
"team": "Tottenham",
|
| 83 |
+
"total_points": 100,
|
| 84 |
+
"latest_predictions": [
|
| 85 |
+
{"gameweek": 21, "total_points": 10, "predicted_points": 8},
|
| 86 |
+
{"gameweek": 20, "total_points": 7, "predicted_points": 6},
|
| 87 |
+
{"gameweek": 19, "total_points": 13, "predicted_points": 10},
|
| 88 |
+
{"gameweek": 18, "total_points": 5, "predicted_points": 4},
|
| 89 |
+
{"gameweek": 17, "total_points": 11, "predicted_points": 9},
|
| 90 |
],
|
| 91 |
+
"predicted_score": 7,
|
| 92 |
},
|
| 93 |
{
|
| 94 |
+
"first_name": "Harry5",
|
| 95 |
+
"second_name": "Kane5",
|
| 96 |
+
"position": "Midfielder",
|
| 97 |
+
"team": "Tottenham",
|
| 98 |
+
"total_points": 100,
|
| 99 |
+
"latest_predictions": [
|
| 100 |
+
{"gameweek": 21, "total_points": 10, "predicted_points": 8},
|
| 101 |
+
{"gameweek": 20, "total_points": 7, "predicted_points": 6},
|
| 102 |
+
{"gameweek": 19, "total_points": 13, "predicted_points": 10},
|
| 103 |
+
{"gameweek": 18, "total_points": 5, "predicted_points": 4},
|
| 104 |
+
{"gameweek": 17, "total_points": 11, "predicted_points": 9},
|
| 105 |
],
|
| 106 |
+
"predicted_score": 7,
|
| 107 |
},
|
| 108 |
]
|
| 109 |
|
| 110 |
+
# Format the latest_predictions for each player
|
| 111 |
+
for player in sample_players:
|
| 112 |
+
player["latest_predictions_str"] = ", ".join(
|
| 113 |
+
[f"GW{pred['gameweek']}: {pred['total_points']} pts (Pred: {pred['predicted_points']} pts)" for pred in player["latest_predictions"]]
|
| 114 |
+
)
|
| 115 |
+
|
| 116 |
|
| 117 |
@app.route("/test")
|
| 118 |
def test():
|
| 119 |
+
return "test"
|
| 120 |
|
| 121 |
|
| 122 |
@app.route("/")
|
| 123 |
def index():
|
| 124 |
"""Render the main page."""
|
| 125 |
+
return render_template("players.html", players=sample_players, current_gameweek=20)
|
| 126 |
|
| 127 |
|
| 128 |
@app.route("/api/players", methods=["POST", "GET"])
|
templates/players.html
CHANGED
|
@@ -3,24 +3,18 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
-
<title>FPL Player Points</title>
|
| 7 |
<script src="https://unpkg.com/htmx.org"></script>
|
| 8 |
<link rel="stylesheet" href="/static/styles.css" />
|
| 9 |
-
<
|
| 10 |
-
<link
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
/>
|
| 14 |
-
<!-- Include DataTables CSS -->
|
| 15 |
-
<link
|
| 16 |
-
href="https://cdn.datatables.net/1.10.21/css/jquery.dataTables.min.css"
|
| 17 |
-
rel="stylesheet"
|
| 18 |
-
/>
|
| 19 |
</head>
|
| 20 |
<body>
|
| 21 |
<div class="container">
|
| 22 |
-
<h1 class="my-4">FPL Player Points</h1>
|
| 23 |
-
|
| 24 |
<!-- Player Table -->
|
| 25 |
<table id="player-table" class="display compact">
|
| 26 |
<thead class="thead-dark">
|
|
@@ -41,40 +35,122 @@
|
|
| 41 |
<td>{{ player.team }}</td>
|
| 42 |
<td>{{ player.total_points }}</td>
|
| 43 |
<td>
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
<th>Predicted Points</th>
|
| 49 |
-
</tr>
|
| 50 |
-
{% for gw in player['5latestGws'] %}
|
| 51 |
-
<tr>
|
| 52 |
-
<td>{{ gw['gameweek'] }}</td>
|
| 53 |
-
<td>{{ gw['total_points'] }}</td>
|
| 54 |
-
<td>{{ gw['predicted_points'] }}</td>
|
| 55 |
-
</tr>
|
| 56 |
-
{% endfor %}
|
| 57 |
-
</table> -->
|
| 58 |
-
</td>
|
| 59 |
<td>{{ player.predicted_score }}</td>
|
| 60 |
</tr>
|
| 61 |
{% endfor %}
|
| 62 |
</tbody>
|
| 63 |
</table>
|
|
|
|
| 64 |
</div>
|
| 65 |
|
| 66 |
<!-- Include jQuery -->
|
| 67 |
-
<script src="https://code.jquery.com/jquery-3.
|
| 68 |
-
<
|
| 69 |
-
<script src="https://cdn.datatables.net/1.10.21/js/jquery.dataTables.min.js"></script>
|
| 70 |
-
<!-- Include Bootstrap JS and dependencies -->
|
| 71 |
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.4/dist/umd/popper.min.js"></script>
|
| 72 |
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
|
|
|
|
|
|
|
|
|
|
| 73 |
|
| 74 |
<script>
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
</body>
|
| 80 |
</html>
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
+
<title>FPL Player Points for week {{ current_gameweek }}</title>
|
| 7 |
<script src="https://unpkg.com/htmx.org"></script>
|
| 8 |
<link rel="stylesheet" href="/static/styles.css" />
|
| 9 |
+
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"/>
|
| 10 |
+
<link rel="stylesheet" href="https://cdn.datatables.net/2.2.0/css/dataTables.dataTables.css" />
|
| 11 |
+
<link rel="stylesheet" href="https://cdn.datatables.net/select/2.1.0/css/select.dataTables.css">
|
| 12 |
+
<link rel="stylesheet" href="https://code.highcharts.com/css/highcharts.css"/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
</head>
|
| 14 |
<body>
|
| 15 |
<div class="container">
|
| 16 |
+
<h1 class="my-4" style="padding-bottom: 6%;">FPL Player Points</h1>
|
| 17 |
+
<h2 class="my-4">Gameweek {{ current_gameweek }}</h2>
|
| 18 |
<!-- Player Table -->
|
| 19 |
<table id="player-table" class="display compact">
|
| 20 |
<thead class="thead-dark">
|
|
|
|
| 35 |
<td>{{ player.team }}</td>
|
| 36 |
<td>{{ player.total_points }}</td>
|
| 37 |
<td>
|
| 38 |
+
{% for prediction in player.latest_predictions %}
|
| 39 |
+
<div>Gameweek {{ prediction.gameweek }}: {{ prediction.total_points }} points (Predicted: {{ prediction.predicted_points }} points)</div>
|
| 40 |
+
{% endfor %}
|
| 41 |
+
</td>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
<td>{{ player.predicted_score }}</td>
|
| 43 |
</tr>
|
| 44 |
{% endfor %}
|
| 45 |
</tbody>
|
| 46 |
</table>
|
| 47 |
+
<canvas id="myChart"></canvas>
|
| 48 |
</div>
|
| 49 |
|
| 50 |
<!-- Include jQuery -->
|
| 51 |
+
<script src="https://code.jquery.com/jquery-3.7.1.js"></script>
|
| 52 |
+
<script src="https://cdn.datatables.net/2.2.0/js/dataTables.js"></script>
|
|
|
|
|
|
|
| 53 |
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.4/dist/umd/popper.min.js"></script>
|
| 54 |
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
|
| 55 |
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
| 56 |
+
<script src="https://cdn.datatables.net/select/2.1.0/js/dataTables.select.js"></script>
|
| 57 |
+
<script src="https://cdn.datatables.net/select/2.1.0/js/select.dataTables.js"></script>
|
| 58 |
|
| 59 |
<script>
|
| 60 |
+
$(document).ready(function() {
|
| 61 |
+
const table = new DataTable('#player-table');
|
| 62 |
+
|
| 63 |
+
// Function to generate random colors
|
| 64 |
+
function getRandomColor() {
|
| 65 |
+
const letters = '0123456789ABCDEF';
|
| 66 |
+
let color = '#';
|
| 67 |
+
for (let i = 0; i < 6; i++) {
|
| 68 |
+
color += letters[Math.floor(Math.random() * 16)];
|
| 69 |
+
}
|
| 70 |
+
return color;
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
// Function to generate chart data for a single player
|
| 74 |
+
function generateChart(player) {
|
| 75 |
+
// Extract gameweeks and points data
|
| 76 |
+
const gameweeks = player.latest_predictions.map(prediction => prediction.gameweek);
|
| 77 |
+
const totalPointsData = player.latest_predictions.map(prediction => prediction.total_points);
|
| 78 |
+
const predictedPointsData = player.latest_predictions.map(prediction => prediction.predicted_points);
|
| 79 |
+
|
| 80 |
+
// Prepare datasets for the chart
|
| 81 |
+
const datasets = [
|
| 82 |
+
{
|
| 83 |
+
label: `${player.first_name} ${player.second_name} - Total Points`,
|
| 84 |
+
data: totalPointsData,
|
| 85 |
+
borderColor: getRandomColor(),
|
| 86 |
+
backgroundColor: 'rgba(0, 123, 255, 0.3)', // Transparent fill color
|
| 87 |
+
fill: '+1' // Fill the area between total and predicted points
|
| 88 |
+
},
|
| 89 |
+
{
|
| 90 |
+
label: `${player.first_name} ${player.second_name} - Predicted Points`,
|
| 91 |
+
data: predictedPointsData,
|
| 92 |
+
borderColor: getRandomColor(),
|
| 93 |
+
fill: false // No fill for predicted points line
|
| 94 |
+
}
|
| 95 |
+
];
|
| 96 |
+
|
| 97 |
+
return { labels: gameweeks, datasets: datasets };
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
// Function to render the chart
|
| 101 |
+
function renderChart(player) {
|
| 102 |
+
const chartData = generateChart(player);
|
| 103 |
+
|
| 104 |
+
// Destroy any existing chart before creating a new one
|
| 105 |
+
if (window.myChart) {
|
| 106 |
+
if (window.myChart instanceof Chart) {
|
| 107 |
+
window.myChart.destroy();
|
| 108 |
+
}
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
// Create a new chart
|
| 112 |
+
const ctx = document.getElementById('myChart').getContext('2d');
|
| 113 |
+
window.myChart = new Chart(ctx, {
|
| 114 |
+
type: 'line',
|
| 115 |
+
data: {
|
| 116 |
+
labels: chartData.labels,
|
| 117 |
+
datasets: chartData.datasets
|
| 118 |
+
},
|
| 119 |
+
options: {
|
| 120 |
+
scales: {
|
| 121 |
+
y: {
|
| 122 |
+
beginAtZero: true
|
| 123 |
+
}
|
| 124 |
+
}
|
| 125 |
+
}
|
| 126 |
+
});
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
// Handle click event on the DataTable rows
|
| 130 |
+
$('#player-table tbody').on('click', 'tr', function() {
|
| 131 |
+
// Get the player's data from the DataTable
|
| 132 |
+
const rowData = table.row(this).data();
|
| 133 |
+
|
| 134 |
+
console.log('Row data:', rowData[0]);
|
| 135 |
+
|
| 136 |
+
// Assuming `playersData` contains all players' data
|
| 137 |
+
const selectedPlayer = playersData.find(player => (player.first_name + " " + player.second_name) === rowData[0]);
|
| 138 |
+
|
| 139 |
+
console.log('Selected player');
|
| 140 |
+
if (selectedPlayer) {
|
| 141 |
+
console.log('Selected player:', selectedPlayer);
|
| 142 |
+
renderChart(selectedPlayer);
|
| 143 |
+
}
|
| 144 |
+
});
|
| 145 |
+
|
| 146 |
+
// Players data injected from the backend
|
| 147 |
+
const playersData = JSON.parse('{{ players | tojson | safe }}');
|
| 148 |
+
|
| 149 |
+
});
|
| 150 |
+
</script>
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
|
| 155 |
</body>
|
| 156 |
</html>
|