| 
									
										
										
										
											2022-10-17 01:08:31 +02:00
										 |  |  | import typing | 
					
						
							| 
									
										
										
										
											2022-04-08 15:52:49 +02:00
										 |  |  | from collections import Counter, defaultdict | 
					
						
							| 
									
										
										
										
											2022-07-27 23:36:20 +02:00
										 |  |  | from colorsys import hsv_to_rgb | 
					
						
							| 
									
										
										
										
											2022-04-08 15:52:49 +02:00
										 |  |  | from datetime import datetime, timedelta, date | 
					
						
							| 
									
										
										
										
											2022-04-09 19:38:08 +02:00
										 |  |  | from math import tau | 
					
						
							| 
									
										
										
										
											2022-04-08 15:52:49 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-17 01:08:31 +02:00
										 |  |  | from bokeh.colors import RGB | 
					
						
							| 
									
										
										
										
											2022-04-08 15:52:49 +02:00
										 |  |  | from bokeh.embed import components | 
					
						
							| 
									
										
										
										
											2022-07-27 23:36:20 +02:00
										 |  |  | from bokeh.models import HoverTool | 
					
						
							| 
									
										
										
										
											2022-04-08 15:52:49 +02:00
										 |  |  | from bokeh.plotting import figure, ColumnDataSource | 
					
						
							|  |  |  | from bokeh.resources import INLINE | 
					
						
							|  |  |  | from flask import render_template | 
					
						
							|  |  |  | from pony.orm import select | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from . import app, cache | 
					
						
							|  |  |  | from .models import Room | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-27 23:36:20 +02:00
										 |  |  | PLOT_WIDTH = 600 | 
					
						
							| 
									
										
										
										
											2022-04-08 15:52:49 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-27 23:36:20 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-05 15:35:46 +02:00
										 |  |  | def get_db_data(known_games: str) -> typing.Tuple[typing.Dict[str, int], typing.Dict[datetime.date, typing.Dict[str, int]]]: | 
					
						
							| 
									
										
										
										
											2022-04-08 15:52:49 +02:00
										 |  |  |     games_played = defaultdict(Counter) | 
					
						
							|  |  |  |     total_games = Counter() | 
					
						
							| 
									
										
										
										
											2022-07-27 23:09:40 +02:00
										 |  |  |     cutoff = date.today()-timedelta(days=30) | 
					
						
							| 
									
										
										
										
											2022-04-09 19:38:08 +02:00
										 |  |  |     room: Room | 
					
						
							| 
									
										
										
										
											2022-04-08 15:52:49 +02:00
										 |  |  |     for room in select(room for room in Room if room.creation_time >= cutoff): | 
					
						
							|  |  |  |         for slot in room.seed.slots: | 
					
						
							| 
									
										
										
										
											2022-08-05 15:35:46 +02:00
										 |  |  |             if slot.game in known_games: | 
					
						
							|  |  |  |                 total_games[slot.game] += 1 | 
					
						
							|  |  |  |                 games_played[room.creation_time.date()][slot.game] += 1 | 
					
						
							| 
									
										
										
										
											2022-04-09 19:38:08 +02:00
										 |  |  |     return total_games, games_played | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-27 23:36:20 +02:00
										 |  |  | def get_color_palette(colors_needed: int) -> typing.List[RGB]: | 
					
						
							|  |  |  |     colors = [] | 
					
						
							|  |  |  |     # colors_needed +1 to prevent first and last color being too close to each other | 
					
						
							|  |  |  |     colors_needed += 1 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for x in range(0, 361, 360 // colors_needed): | 
					
						
							|  |  |  |         # a bit of noise on value to add some luminosity difference | 
					
						
							|  |  |  |         colors.append(RGB(*(val * 255 for val in hsv_to_rgb(x / 360, 0.8, 0.8 + (x / 1800))))) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # splice colors for maximum hue contrast. | 
					
						
							|  |  |  |     colors = colors[::2] + colors[1::2] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return colors | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def create_game_played_figure(all_games_data: typing.Dict[datetime.date, typing.Dict[str, int]], | 
					
						
							|  |  |  |                               game: str, color: RGB) -> figure: | 
					
						
							|  |  |  |     occurences = [] | 
					
						
							|  |  |  |     days = [day for day, game_data in all_games_data.items() if game_data[game]] | 
					
						
							|  |  |  |     for day in days: | 
					
						
							|  |  |  |         occurences.append(all_games_data[day][game]) | 
					
						
							|  |  |  |     data = { | 
					
						
							|  |  |  |         "days": [datetime.combine(day, datetime.min.time()) for day in days], | 
					
						
							|  |  |  |         "played": occurences | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     plot = figure( | 
					
						
							|  |  |  |         title=f"{game} Played Per Day", x_axis_type='datetime', x_axis_label="Date", | 
					
						
							|  |  |  |         y_axis_label="Games Played", sizing_mode="scale_both", width=PLOT_WIDTH, height=500, | 
					
						
							|  |  |  |         toolbar_location=None, tools="", | 
					
						
							|  |  |  |         # setting legend to False seems broken in bokeh currently? | 
					
						
							|  |  |  |         # legend=False | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     hover = HoverTool(tooltips=[("Date:", "@days{%F}"), ("Played:", "@played")], formatters={"@days": "datetime"}) | 
					
						
							|  |  |  |     plot.add_tools(hover) | 
					
						
							|  |  |  |     plot.vbar(x="days", top="played", legend_label=game, color=color, source=ColumnDataSource(data=data), width=1) | 
					
						
							|  |  |  |     return plot | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-09 19:38:08 +02:00
										 |  |  | @app.route('/stats') | 
					
						
							| 
									
										
										
										
											2022-07-27 23:36:20 +02:00
										 |  |  | @cache.memoize(timeout=60 * 60)  # regen once per hour should be plenty | 
					
						
							| 
									
										
										
										
											2022-04-09 19:38:08 +02:00
										 |  |  | def stats(): | 
					
						
							| 
									
										
										
										
											2022-08-05 15:35:46 +02:00
										 |  |  |     from worlds import network_data_package | 
					
						
							|  |  |  |     known_games = set(network_data_package["games"]) | 
					
						
							| 
									
										
										
										
											2022-04-13 23:46:15 -04:00
										 |  |  |     plot = figure(title="Games Played Per Day", x_axis_type='datetime', x_axis_label="Date", | 
					
						
							| 
									
										
										
										
											2022-07-27 23:36:20 +02:00
										 |  |  |                   y_axis_label="Games Played", sizing_mode="scale_both", width=PLOT_WIDTH, height=500) | 
					
						
							| 
									
										
										
										
											2022-04-09 19:38:08 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-05 15:35:46 +02:00
										 |  |  |     total_games, games_played = get_db_data(known_games) | 
					
						
							| 
									
										
										
										
											2022-04-08 15:52:49 +02:00
										 |  |  |     days = sorted(games_played) | 
					
						
							| 
									
										
										
										
											2022-04-09 19:38:08 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-27 23:36:20 +02:00
										 |  |  |     color_palette = get_color_palette(len(total_games)) | 
					
						
							|  |  |  |     game_to_color: typing.Dict[str, RGB] = {game: color for game, color in zip(total_games, color_palette)} | 
					
						
							| 
									
										
										
										
											2022-04-09 19:38:08 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     for game in sorted(total_games): | 
					
						
							| 
									
										
										
										
											2022-04-08 15:52:49 +02:00
										 |  |  |         occurences = [] | 
					
						
							|  |  |  |         for day in days: | 
					
						
							|  |  |  |             occurences.append(games_played[day][game]) | 
					
						
							|  |  |  |         plot.line([datetime.combine(day, datetime.min.time()) for day in days], | 
					
						
							| 
									
										
										
										
											2022-07-27 23:36:20 +02:00
										 |  |  |                   occurences, legend_label=game, line_width=2, color=game_to_color[game]) | 
					
						
							| 
									
										
										
										
											2022-04-09 19:38:08 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-08 15:52:49 +02:00
										 |  |  |     total = sum(total_games.values()) | 
					
						
							| 
									
										
										
										
											2022-04-13 23:46:15 -04:00
										 |  |  |     pie = figure(plot_height=350, title=f"Games Played in the Last 30 Days (Total: {total})", toolbar_location=None, | 
					
						
							| 
									
										
										
										
											2022-04-08 15:52:49 +02:00
										 |  |  |                  tools="hover", tooltips=[("Game:", "@games"), ("Played:", "@count")], | 
					
						
							| 
									
										
										
										
											2022-07-27 23:36:20 +02:00
										 |  |  |                  sizing_mode="scale_both", width=PLOT_WIDTH, height=500, x_range=(-0.5, 1.2)) | 
					
						
							| 
									
										
										
										
											2022-04-08 15:52:49 +02:00
										 |  |  |     pie.axis.visible = False | 
					
						
							| 
									
										
										
										
											2022-07-27 23:36:20 +02:00
										 |  |  |     pie.xgrid.visible = False | 
					
						
							|  |  |  |     pie.ygrid.visible = False | 
					
						
							| 
									
										
										
										
											2022-04-08 15:52:49 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     data = { | 
					
						
							|  |  |  |         "games": [], | 
					
						
							|  |  |  |         "count": [], | 
					
						
							|  |  |  |         "start_angles": [], | 
					
						
							|  |  |  |         "end_angles": [], | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     current_angle = 0 | 
					
						
							|  |  |  |     for i, (game, count) in enumerate(total_games.most_common()): | 
					
						
							|  |  |  |         data["games"].append(game) | 
					
						
							|  |  |  |         data["count"].append(count) | 
					
						
							|  |  |  |         data["start_angles"].append(current_angle) | 
					
						
							| 
									
										
										
										
											2022-04-09 19:38:08 +02:00
										 |  |  |         angle = count / total * tau | 
					
						
							| 
									
										
										
										
											2022-04-08 15:52:49 +02:00
										 |  |  |         current_angle += angle | 
					
						
							|  |  |  |         data["end_angles"].append(current_angle) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-27 23:36:20 +02:00
										 |  |  |     data["colors"] = [game_to_color[game] for game in data["games"]] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     pie.wedge(x=0, y=0, radius=0.5, | 
					
						
							| 
									
										
										
										
											2022-04-08 15:52:49 +02:00
										 |  |  |               start_angle="start_angles", end_angle="end_angles", fill_color="colors", | 
					
						
							|  |  |  |               source=ColumnDataSource(data=data), legend_field="games") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-27 23:36:20 +02:00
										 |  |  |     per_game_charts = [create_game_played_figure(games_played, game, game_to_color[game]) for game in total_games | 
					
						
							|  |  |  |                        if total_games[game] > 1] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     script, charts = components((plot, pie, *per_game_charts)) | 
					
						
							| 
									
										
										
										
											2022-04-08 15:52:49 +02:00
										 |  |  |     return render_template("stats.html", js_resources=INLINE.render_js(), css_resources=INLINE.render_css(), | 
					
						
							| 
									
										
										
										
											2022-04-13 23:51:44 -04:00
										 |  |  |                            chart_data=script, charts=charts) |