From 3eec6069d2f0f2badaba94ef60a48c6dd4199afe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20V=C3=A4ist=C3=B6?= <maxvaist@ad.helsinki.fi> Date: Mon, 16 Oct 2023 12:57:36 +0300 Subject: [PATCH] Made the project multi-page --- dash_plot_generation/app.py | 110 ++++ .../background_image.jpg | Bin .../{dash_assets => assets}/broken car.jpg | Bin .../{dash_assets => assets}/dark city.jpg | Bin .../dark_computer_room.jpg | Bin .../{dash_assets => assets}/radioactive.jpg | Bin .../{dash_assets => assets}/styles.css | 38 +- dash_plot_generation/dash_plot_generator.py | 619 ------------------ dash_plot_generation/data_store.py | 38 ++ .../pages/__pycache__/about.cpython-311.pyc | Bin 0 -> 1117 bytes .../dash_plot_generator.cpython-311.pyc | Bin 0 -> 14146 bytes .../pages/__pycache__/home.cpython-311.pyc | Bin 0 -> 643 bytes dash_plot_generation/pages/about.py | 21 + .../pages/dash_plot_generator.py | 314 +++++++++ dash_plot_generation/pages/home.py | 11 + .../pages/technical_report.py | 0 dash_plot_generation/styles_and_handles.py | 72 ++ dash_plot_generation/utils.py | 82 +++ 18 files changed, 675 insertions(+), 630 deletions(-) create mode 100644 dash_plot_generation/app.py rename dash_plot_generation/{dash_assets => assets}/background_image.jpg (100%) rename dash_plot_generation/{dash_assets => assets}/broken car.jpg (100%) rename dash_plot_generation/{dash_assets => assets}/dark city.jpg (100%) rename dash_plot_generation/{dash_assets => assets}/dark_computer_room.jpg (100%) rename dash_plot_generation/{dash_assets => assets}/radioactive.jpg (100%) rename dash_plot_generation/{dash_assets => assets}/styles.css (76%) delete mode 100644 dash_plot_generation/dash_plot_generator.py create mode 100644 dash_plot_generation/data_store.py create mode 100644 dash_plot_generation/pages/__pycache__/about.cpython-311.pyc create mode 100644 dash_plot_generation/pages/__pycache__/dash_plot_generator.cpython-311.pyc create mode 100644 dash_plot_generation/pages/__pycache__/home.cpython-311.pyc create mode 100644 dash_plot_generation/pages/about.py create mode 100644 dash_plot_generation/pages/dash_plot_generator.py create mode 100644 dash_plot_generation/pages/home.py create mode 100644 dash_plot_generation/pages/technical_report.py create mode 100644 dash_plot_generation/styles_and_handles.py diff --git a/dash_plot_generation/app.py b/dash_plot_generation/app.py new file mode 100644 index 0000000..904843d --- /dev/null +++ b/dash_plot_generation/app.py @@ -0,0 +1,110 @@ +import dash +import pandas +from dash import html, dash, Output, Input +from dash.exceptions import PreventUpdate + +from dash_plot_generation.data_store import initialize_data, OWNER_RANGE_PARTS_SORTED, FULL_DATA +from dash_plot_generation.utils import get_average_user_rating_label, get_game_count_label, get_top_revenue_game_labels, \ + get_total_revenue_label, get_top_genre_labels, get_ccu_label, get_average_game_rev_label +from dash_plot_generation.styles_and_handles import RATING_MIN_REVIEWS, RATING_SLIDER, RATING_TABLE, \ + RATING_DISTRIBUTION_PLOT, DEV_AVERAGE_RATING_LABEL, DENSITY_LAYOUT_STYLE, WHITE_STEAM, TAB_COLOR, TAB_EDGE, \ + TAB_HEADER_COLOR, DEVELOPER_DROPDOWN, DEV_TOP_GENRES_LABEL, DEV_CCU_LABEL, DEV_GAME_COUNT_LABEL, \ + DEV_REV_PER_GAME_LABEL, DEV_REVENUE_LABEL, DEV_TOP_GAMES +from visual_presentation.Distribution_of_review_rating import get_rating_density_plot + +APP = dash.Dash( + name=__name__, + use_pages=True, + external_stylesheets=['/assets/styles.css', + 'https://codepen.io/chriddyp/pen/bWLwgP.css'] +) + +# APP.css.append_css({ +# 'external_url': 'styles.css' +# }) + +APP.layout = html.Div([ + html.Nav(className="navbar", children=[ + html.H6("SteamSavvy - Steam game data insights", + style={"margin-left": "60px", "display": "inline-block"}, + className="nav-item-1"), + html.A('About', className="nav-item nav-link btn", href='/about', + style={"margin-left": "150px"},), + html.A('Dashboard', className="nav-item nav-link btn", href='/dashboard', + style={"margin-left": "150px"}), + html.A('Technical report', className="nav-item nav-link active btn", href='/Documentation/Report', + style={"margin-left": "150px"}) + ]), + + dash.page_container +], className="body") + + +@APP.callback([Output(DEV_REVENUE_LABEL, "children"), + Output(DEV_TOP_GENRES_LABEL, "children"), + Output(DEV_CCU_LABEL, "children"), + Output(DEV_GAME_COUNT_LABEL, "children"), + Output(DEV_REV_PER_GAME_LABEL, "children"), + Output(DEV_TOP_GAMES, "children"), + Output(DEV_AVERAGE_RATING_LABEL, "children")], + inputs=[Input(DEVELOPER_DROPDOWN, "value")]) +def update_dev_info(dev_name): + global FULL_DATA, OWNER_RANGE_PARTS_SORTED + if not (dev_name and isinstance(FULL_DATA, pandas.DataFrame)): + raise PreventUpdate + + # Remove empty rows + mask = FULL_DATA.developer.apply(lambda x: dev_name in x if isinstance(x, str) else False) + dev_data = FULL_DATA[mask] + + # Engineer revenue data into the dataframe + # add_game_revenues_and_owner_means(dev_data) + + # Top games + dev_top_games_label = get_top_revenue_game_labels(dev_data) + + # Dev total revenue + dev_revenue = get_total_revenue_label(dev_data) + + # Dev revenue per game + dev_game_revenue_per_game = get_average_game_rev_label(dev_data) + + # Top genres + dev_top_genre_labels = get_top_genre_labels(dev_data) + + # CCU + dev_ccu = get_ccu_label(dev_data) + + # Game count + dev_game_count = get_game_count_label(dev_data) + + user_rating_value = get_average_user_rating_label(dev_data) + return dev_revenue, dev_top_genre_labels, dev_ccu, dev_game_count, dev_game_revenue_per_game, dev_top_games_label, \ + user_rating_value + + +@APP.callback(Output(RATING_DISTRIBUTION_PLOT, "figure"), + Output("Game pop bottom_region", 'children'), + Input(RATING_SLIDER, "value"), + Input(RATING_MIN_REVIEWS, "value")) +def update_density_filter_plot(rating_range, min_reviews): + allowed_indexes = [str_val for (val, str_val) in OWNER_RANGE_PARTS_SORTED[rating_range[0]:rating_range[1] + 1]] + allowed_ratings = [" .. ".join([val, allowed_indexes[i + 1]]) for (i, val) in enumerate(allowed_indexes) + if i < len(allowed_indexes) - 1] + + output = get_rating_density_plot(FULL_DATA, allowed_ratings, min_reviews, layout=DENSITY_LAYOUT_STYLE) + table = html.Div(dash.dash_table.DataTable(output['top_games']['non_free'].to_dict('records'), + id=RATING_TABLE, + + style_data={'backgroundColor': TAB_COLOR, + 'color': WHITE_STEAM, + 'border': '1px solid ' + TAB_EDGE}, + style_header={'backgroundColor': TAB_HEADER_COLOR, + 'color': WHITE_STEAM, + 'border': '1px solid ' + TAB_EDGE})) + return output['fig'], [table] + + +if __name__ == "__main__": + initialize_data() + APP.run_server(debug=True, host="0.0.0.0") diff --git a/dash_plot_generation/dash_assets/background_image.jpg b/dash_plot_generation/assets/background_image.jpg similarity index 100% rename from dash_plot_generation/dash_assets/background_image.jpg rename to dash_plot_generation/assets/background_image.jpg diff --git a/dash_plot_generation/dash_assets/broken car.jpg b/dash_plot_generation/assets/broken car.jpg similarity index 100% rename from dash_plot_generation/dash_assets/broken car.jpg rename to dash_plot_generation/assets/broken car.jpg diff --git a/dash_plot_generation/dash_assets/dark city.jpg b/dash_plot_generation/assets/dark city.jpg similarity index 100% rename from dash_plot_generation/dash_assets/dark city.jpg rename to dash_plot_generation/assets/dark city.jpg diff --git a/dash_plot_generation/dash_assets/dark_computer_room.jpg b/dash_plot_generation/assets/dark_computer_room.jpg similarity index 100% rename from dash_plot_generation/dash_assets/dark_computer_room.jpg rename to dash_plot_generation/assets/dark_computer_room.jpg diff --git a/dash_plot_generation/dash_assets/radioactive.jpg b/dash_plot_generation/assets/radioactive.jpg similarity index 100% rename from dash_plot_generation/dash_assets/radioactive.jpg rename to dash_plot_generation/assets/radioactive.jpg diff --git a/dash_plot_generation/dash_assets/styles.css b/dash_plot_generation/assets/styles.css similarity index 76% rename from dash_plot_generation/dash_assets/styles.css rename to dash_plot_generation/assets/styles.css index 17e9629..6ace393 100644 --- a/dash_plot_generation/dash_assets/styles.css +++ b/dash_plot_generation/assets/styles.css @@ -1,8 +1,30 @@ -html, body { - height: 100%; - margin: 0; - background-color: rgb(27,40,56); +body { + margin: 0; + padding: 0; + object-fit: fill; + height: 100%; + width: 100%; + background-color: rgb(27,40,56); + color: rgb(235,235,235); + +} + +.navbar { + background-color: rgb(23,29,37); + color: rgb(197,195,192); + width: 100%; + margin:0; +} + +.nav-item-1 { + color: rgb(197,195,192); + font-size: 30px; +} + +.nav-link { + color: rgb(197,195,192); + font-size: 20px; } #app-container { height: 100%; @@ -43,9 +65,6 @@ html, body { border-bottom: 2px solid rgb(37,55,77); } -.dark_plot{ - background-color: black; -} /* Track rgb(197,195,192) */ div.scrollable::-webkit-scrollbar { @@ -55,10 +74,7 @@ div.scrollable::-webkit-scrollbar { } div.scrollable::-webkit-scrollbar-track { - margin-top: 20px; - margin-bottom: 20px; - margin-left: 20px; - margin-right: 20px; + margin: 20px; } div.scrollable::-webkit-scrollbar-thumb { diff --git a/dash_plot_generation/dash_plot_generator.py b/dash_plot_generation/dash_plot_generator.py deleted file mode 100644 index 419fba3..0000000 --- a/dash_plot_generation/dash_plot_generator.py +++ /dev/null @@ -1,619 +0,0 @@ - -import os - -import numpy -import pandas -from dash import dash, dcc, html, Output, Input -from collections import Counter - -from dash_plot_generation.utils import split_companies, extract_unique_companies, convert_owners_to_limits, \ - get_owner_means, round_to_three_largest_digits, replace_owner_number_with_symbol_real_numeric, \ - convert_to_numeric_str, label_with_rev, label_with_text -from dash.exceptions import PreventUpdate -import plotly.graph_objects as go -import plotly.express as px - -from visual_presentation.Distribution_of_review_rating import get_rating_density_plot - -RATING_MIN_REVIEWS = "min_reviews_id" - -RATING_SLIDER = "rating_slider" - -RATING_TABLE = "rating_data_table" - -RATING_DISTRIBUTION_PLOT = "game_popularity_density_plot" - -MAIN_PANEL_TAB_DICT = {'height': '550px', 'width': '100%', 'margin': '0', 'overflow': 'auto'} - -SPACE_NORMAL_ENTRY = 35 - -DEV_AVERAGE_RATING_LABEL = "dev_average_rating" - -RIGHT_SIDE_TEXT_DICT = {'display': 'inline-block', - 'float': 'right', 'margin-right': '0%', 'margin-bottom': '0px'} - -DEFAULT_PLOT_STYLE_DICT = dict( - template="plotly_dark", - plot_bgcolor="rgb(31,46,65)", - paper_bgcolor="rgb(31,46,65)", -) - -DENSITY_LAYOUT_STYLE = DEFAULT_PLOT_STYLE_DICT | dict( - title='Distribution of Game Review Rating', - xaxis_title="Game user rating", - yaxis_title="Proportion" - -) - -DARK_STEAM = "rgb(23,29,37)" -WHITE_STEAM = "rgb(235,235,235)" -TITLE_WHITE_STEAM = "rgb(197,195,192)" -DARK_BLUE_STEAM = "rgb(27,40,56)" -TAB_COLOR = "rgb(31,46,65)" -TAB_EDGE = "rgb(37,55,77)" -DROPDOWN_COLOR = "rgb(50,70,101" -SMALL_PANEL_COLOR = "rgb(22,32,45)" - -layout = dict(template="plotly_dark", plot_bgcolor="rgb(31,46,65)", paper_bgcolor="rgb(31,46,65)") -DEFAULT_TABS_DICT = {'width': 'auto', 'display': 'flex', - 'background-color': TAB_COLOR, 'border-color': TAB_EDGE} -TAB_HEADER_COLOR = "rgb(45,96,150)" -DEVELOPER_DROPDOWN = "developer_dropdown" -TAB_NORMAL_DICT = {'background-color': TAB_COLOR, 'color': TITLE_WHITE_STEAM, - 'border': '0px solid', - 'font-size': '15px', - 'border_bottom': '2px solid ' + TAB_EDGE} -TAB_HIGHLIGHT_DICT = {'backgroundColor': TAB_HEADER_COLOR, 'color': 'white', "border-color": "transparent", - 'font-size': '15px'} -PANEL_DEFAULT_DICT = {'display': 'inline-block', - 'background-color': TAB_COLOR, 'border': '2px solid', 'border-color': TAB_EDGE, - 'color': WHITE_STEAM, 'height': '600px'} -SMALL_PANEL_DICT = {'float': 'left', 'background-color': 'transparent', 'box-sizing': 'border-box', - 'padding': '10px'} -SMALL_TAB_PANEL_DICT = SMALL_PANEL_DICT | {'width': '48%', 'height': '100%', - 'margin-bottom': '50px', - 'padding-top': '4%', 'padding-bottom': '5%', 'padding-left': '5%', - 'padding-right': '5%', - 'margin-top': '20px' - } -SMALL_PANEL_HEADER_DICT = {'text-align': 'center', 'padding-top': '5%', 'padding-bottom': '2%'} - -DEV_TOP_GENRES_LABEL = "dev_top_genres" - -LIST_DICT = {'display': 'inline-block', 'margin-bottom': '0px', 'padding-right': '0px'} - -NORMAL_DIVISION_DICT = SMALL_PANEL_DICT | {'width': '60%', 'height': '100%', 'margin-right': '5%', 'padding-left': '5%', - 'margin-bottom': '5%', 'background-color': TAB_COLOR} - -DEV_CCU_LABEL = "dev_ccu" - -DEV_GAME_COUNT_LABEL = "dev_game_count" - -DEV_REV_PER_GAME_LABEL = "dev_rev_per_game" - -DEV_REVENUE_LABEL = "dev_revenue" - -DEV_TOP_GAMES = "pub_top_games" - -PUB_TOP_GENRES_LABEL = "pub_top_genres" - -PUB_CCU_LABEL = "pub_ccu" - -PUB_GAME_COUNT_LABEL = "pub_game_count" - -PUB_REV_PER_GAME_LABEL = "pub_rev_per_game" - -PUB_REVENUE_LABEL = "pub_revenue" - -PUB_TOP_GAMES = "pub_top_games" -DEMO_PLOT_LABELS = ["Action", "Adventure", "RPG", "Puzzle", "Strategy", "Other"] -DEMO_PLOT_COLORS = list(zip(DEMO_PLOT_LABELS, px.colors.qualitative.G10)) -csv_path = os.path.normpath(os.getcwd() + os.sep + os.pardir + os.sep + "api_exploration") -split_csv_path = os.path.join(csv_path, "file_segments") -# steam_dark_template = dict(layout=go.Layout(title_font)) - -FULL_DATA = None -OWNER_RANGE_PARTS_SORTED = None - -DASH_ASSETS_PATH = "dash_assets" - -APP = dash.Dash( - name=__name__, - assets_folder=DASH_ASSETS_PATH, - external_stylesheets=['https://codepen.io/chriddyp/pen/bWLwgP.css'], -) - - -def initialize_data(): - global FULL_DATA, OWNER_RANGE_PARTS_SORTED - dataframe = None - for file in os.listdir(split_csv_path): - file_path = os.path.join(split_csv_path, file) - dataframe = pandas.concat([dataframe, pandas.read_csv(file_path)]) if dataframe is not None \ - else pandas.read_csv(file_path) - - add_game_revenues_and_owner_means(dataframe) - owner_ranges = {value_range for value_range in dataframe["owners"].unique()} - - ranges_test = [(convert_owners_to_limits(value_range), value_range.split(" .. ")) for value_range in owner_ranges] - unique_owner_values = {(limits[i], limits_str[i]) for (limits, limits_str) - in ranges_test for i in range(2)} - sorted_owner_list = sorted(unique_owner_values, key=lambda range: range[0]) - - OWNER_RANGE_PARTS_SORTED = sorted_owner_list - FULL_DATA = dataframe - - -@APP.callback(Output(RATING_DISTRIBUTION_PLOT, "figure"), - Output("Game pop bottom_region", 'children'), - Input(RATING_SLIDER, "value"), - Input(RATING_MIN_REVIEWS, "value")) -def update_density_filter_plot(rating_range, min_reviews): - global FULL_DATA, OWNER_RANGE_PARTS_SORTED - - allowed_indexes = [str_val for (val, str_val) in OWNER_RANGE_PARTS_SORTED[rating_range[0]:rating_range[1] + 1]] - allowed_ratings = [" .. ".join([val, allowed_indexes[i + 1]]) for (i, val) in enumerate(allowed_indexes) - if i < len(allowed_indexes) - 1] - - output = get_rating_density_plot(FULL_DATA, allowed_ratings, min_reviews, layout=DENSITY_LAYOUT_STYLE) - table = html.Div(dash.dash_table.DataTable(output['top_games']['non_free'].to_dict('records'), - id=RATING_TABLE, - - style_data={'backgroundColor': TAB_COLOR, - 'color': WHITE_STEAM, - 'border': '1px solid ' + TAB_EDGE}, - style_header={'backgroundColor': TAB_HEADER_COLOR, - 'color': WHITE_STEAM, - 'border': '1px solid ' + TAB_EDGE})) - return output['fig'], [table] - - -@APP.callback([Output(DEV_REVENUE_LABEL, "children"), - Output(DEV_TOP_GENRES_LABEL, "children"), - Output(DEV_CCU_LABEL, "children"), - Output(DEV_GAME_COUNT_LABEL, "children"), - Output(DEV_REV_PER_GAME_LABEL, "children"), - Output(DEV_TOP_GAMES, "children"), - Output(DEV_AVERAGE_RATING_LABEL, "children")], - inputs=[Input(DEVELOPER_DROPDOWN, "value")]) -def update_dev_info(dev_name): - global FULL_DATA, OWNER_RANGE_PARTS_SORTED - if not (dev_name and isinstance(FULL_DATA, pandas.DataFrame)): - raise PreventUpdate - - # Remove empty rows - mask = FULL_DATA.developer.apply(lambda x: dev_name in x if isinstance(x, str) else False) - dev_data = FULL_DATA[mask] - - # Engineer revenue data into the dataframe - # add_game_revenues_and_owner_means(dev_data) - - # Top games - dev_top_games_label = get_top_revenue_game_labels(dev_data) - - # Dev total revenue - dev_revenue = get_total_revenue_label(dev_data) - - # Dev revenue per game - dev_game_revenue_per_game = get_average_game_rev_label(dev_data) - - # Top genres - dev_top_genre_labels = get_top_genre_labels(dev_data) - - # CCU - dev_ccu = get_ccu_label(dev_data) - - # Game count - dev_game_count = get_game_count_label(dev_data) - - user_rating_value = get_average_user_rating_label(dev_data) - return dev_revenue, dev_top_genre_labels, dev_ccu, dev_game_count, dev_game_revenue_per_game, dev_top_games_label, \ - user_rating_value - - -def get_average_user_rating_label(dev_data): - value_str = str(round(100 * dev_data["Review_rating"].mean())) + "%" - label = label_with_text("Average game rating", value_str, SPACE_NORMAL_ENTRY, ".") - return label - - -def get_game_count_label(dev_data): - return label_with_text("Number of games", str(dev_data.shape[0]), SPACE_NORMAL_ENTRY, ".") - - -def add_game_revenues_and_owner_means(data): - data["owner_means"] = data["owners"].apply(lambda x: get_owner_means(convert_owners_to_limits(x))) - data["game_revenue"] = data.apply(lambda x: x["owner_means"] * x["price"] if - not (pandas.isna(x["owner_means"]) or pandas.isna(x["price"])) - else 0, axis=1) - - -def get_top_revenue_game_labels(data): - top_games = data.sort_values(by=["game_revenue"], ascending=False).head(3) - top_games_processed = top_games.apply(lambda x: label_with_rev(x["name"], x["game_revenue"], SPACE_NORMAL_ENTRY, - ".", "$"), axis=1) - dev_top_games_with_dot = [" ".join(["•", game]) for game in top_games_processed] - dev_top_games_label = html.Div("\n".join(dev_top_games_with_dot), - style={'white-space': 'pre-line', 'padding-left': '5%'}) - return dev_top_games_label - - -def get_total_revenue_label(data): - top_games_processed = label_with_rev("• Total", numpy.nansum(data["game_revenue"]), SPACE_NORMAL_ENTRY, ".", "$") - return top_games_processed - - -def get_top_genre_labels(data): - genre_totals = [genre for genre_list in data["genres"] if isinstance(genre_list, str) - for genre in genre_list.split(", ")] - genre_counts = Counter(genre_totals).most_common(3) - top_genres_rows = [label_with_text(genre[0], str(genre[1]), 50, ".") for genre in genre_counts] - top_genres_with_dot = [" ".join(["•", row]) for row in top_genres_rows] - top_genre_labels = html.Div("\n".join(top_genres_with_dot), - style={'white-space': 'pre-line', 'padding-left': '5%'}) - return top_genre_labels - - -def get_ccu_label(data): - ccu = sum(data["ccu"]) - dev_ccu = convert_to_numeric_str(ccu) - - return label_with_text("Concurrent users", dev_ccu, SPACE_NORMAL_ENTRY, ".") - - -def get_genre_popularity_counts(df, group_after_largest=8): - genre_df = df[["genres", "owner_means", "game_revenue"]] - genre_owners = {} - genre_revenue = {} - - for index, row in genre_df.iterrows(): - if not isinstance(row.genres, str): - continue - genre_list = row.genres.split(", ") - for genre in genre_list: - if genre in genre_owners.keys(): - genre_owners[genre] += row["owner_means"] - genre_revenue[genre] += row["game_revenue"] - else: - genre_owners[genre] = row["owner_means"] - genre_revenue[genre] = row["game_revenue"] - top_owners = dict(Counter(genre_owners).most_common(group_after_largest)) - top_revenue = dict(Counter(genre_revenue).most_common(group_after_largest)) - top_owners["Other"] = sum([val for (key, val) in genre_owners.items() - if key not in top_owners.keys()]) - top_revenue["Other"] = sum([val for (key, val) in genre_revenue.items() - if key not in top_revenue.keys()]) - - return top_owners, top_revenue - - -def get_average_game_rev_label(data): - game_revenue_per_game_raw = numpy.nansum(data["game_revenue"]) / len(data["game_revenue"]) - dev_game_revenue_per_game_row = label_with_rev("Average", game_revenue_per_game_raw, SPACE_NORMAL_ENTRY, ".", "$") - dev_game_revenue_per_game = " ".join(["•", dev_game_revenue_per_game_row]) - return dev_game_revenue_per_game - - -def initialize_dash(host: str = "0.0.0.0", **kwargs): - """ - Runs the Dash server. - Args: - host: IP address of the server - kwargs: Variables which are passed down to APP.run function as named arguments. - Note: - The server IP is not actually 0.0.0.0; if the dash app is not accessible via this address, use the same port - number but replace the IP address with your local network IP instead. - """ - - global APP, FULL_DATA, DEMO_PLOT_COLORS, DEMO_PLOT_LABELS, OWNER_RANGE_PARTS_SORTED - - # unique_publishers = extract_unique_companies(df["publisher"].apply(lambda x: split_companies(x))) - # unique_developers = extract_unique_companies(df["developer"].iloc[0:10].apply(lambda x: split_companies(x))) - unique_publishers = ["Valve"] - unique_developers = ["Valve"] - - # Genre performance table_values - # genre_owners, genre_revenue = get_genre_popularity_counts(FULL_DATA, 6) - genre_owners = {key: val for (key, val) in - zip(["Action", "Adventure", "RPG", "Puzzle", "Strategy", "Other"], - [0.7, 0.5, 0.1, 0.4, 0.3, 0.7])} - genre_revenue = {key: val for (key, val) in - zip(["Action", "Adventure", "RPG", "Puzzle", "Strategy", "Other"], - [0.5, 0.4, 0.3, 0.4, 0.6, 0.7])} - - # Game popularity filter values - max_reviews = numpy.nanmax(FULL_DATA.apply(lambda x: x["positive"] + x["negative"], axis=1)) - owner_range_dict = {index: val_str for (index, (val, val_str)) in enumerate(OWNER_RANGE_PARTS_SORTED)} - min_owner = min(owner_range_dict.keys()) - max_owner = max(owner_range_dict.keys()) - - APP.css.append_css({ - 'external_url': 'styles.css' - }) - APP.layout = html.Div( - children=[ - html.Nav(className="nav nav-pills", children=[ - html.H6("SteamSavvy - Steam game data insights", - style={"margin-left": "60px", "width": "20%", "display": "inline-block"}), - html.A('About', className="nav-item nav-link btn", href='/Documentation/About', - style={"margin-left": "300px"}), - html.A('Technical report', className="nav-item nav-link active btn", href='/Documentation/Report', - style={"margin-left": "150px"}) - ], - style={"background-color": "rgb(23,29,37)", "color": "rgb(197,195,192)", 'width': '100%'}), - html.Div(className="row", children=[ - html.Div(children=[ - dcc.Tabs(id="main_plots", value="tab1", children=[ - dcc.Tab(label="Genre performance", value="tab1", - style=TAB_NORMAL_DICT, selected_style=TAB_HIGHLIGHT_DICT, - children=[ - html.Div(children=[ - html.Div( - children=[ - html.Div(style=NORMAL_DIVISION_DICT, - children=[dcc.Graph( - figure=go.Figure(data=[ - {'x': ["Action", "Adventure", "RPG", "Puzzle", - "Strategy"], - 'y': [0.7, 0.4, 0.8, 1.2, 1.3], - 'type': 'bar'}, - ], - layout=DEFAULT_PLOT_STYLE_DICT | - dict(title="Relative genre perfomance") - ), - - ), - html.P(f"""Genre performance measures the assessed - exploitability of the specific game genre. The assessment - is done by estimating the genre popularity, and games - developed in the next two years and showing the relative - differences between the genres.""")]), - html.Div(style=SMALL_PANEL_DICT | {'width': '35%', 'height': '100%', - 'background-color': TAB_COLOR}, - children=[ - html.Div(children=[ - html.Div(style={'width': '100%', 'height': '50%'}, - children=[dcc.Graph( - figure=go.Figure(data=[go.Pie( - labels=list(genre_owners.keys()), - values=list(genre_owners.values()), - sort=False)], - layout=DEFAULT_PLOT_STYLE_DICT | - dict(title="Genre popularity", - margin=dict(l=20, r=20, - t=50, b=20))), - style={'width': '100%', - 'height': '100%'})]), - html.Div(style={'width': '100%', 'height': '50%'}, - children=[dcc.Graph( - figure=go.Figure(data=[go.Pie( - labels=list(genre_revenue.keys()), - values=list(genre_revenue.values()), - sort=False)], - layout=DEFAULT_PLOT_STYLE_DICT | - dict( - title="Genre revenue share", - margin=dict(l=20, r=20, - t=50, b=20))), - style={'width': '100%', - 'height': '100%'})] - ) - - ], style={'height': '540px'} - ), - ] - ) - ] - ), - - html.Div(children=[ - html.H5("Genre prediction", style={'margin-bottom': '50px'}), - html.Div(children=[ - html.Div(children=[ - html.P("Selected genre:", style={'margin-bottom': '10px'}), - dcc.Dropdown(id="genre_dropdown", value="action", - options=[{"label": html.Span([genre], - style={ - 'color': WHITE_STEAM}), - "value": genre} for genre in - ["action"]], - style={'color': WHITE_STEAM, 'display': 'inline-block', - 'width': '50%'}, - className='dash-dropdown', - ), - - ], - style={'width': '100%', 'margin-bottom': '50px'}) - ]), - dcc.Graph(figure=go.Figure(layout=DEFAULT_PLOT_STYLE_DICT | - dict(title="Genre prediction plot", - margin=dict(l=20, r=20, - t=50, b=20)))), - html.P("""This is an individual regression estimate for the genre that represents - the estimated amount of games to be produced in the next two years""") - - ], style=NORMAL_DIVISION_DICT | {'width': '90%'}) - - ], - style=MAIN_PANEL_TAB_DICT, - className="scrollable") - ]), - dcc.Tab(label="Game popularity", value="tab2", - style=TAB_NORMAL_DICT, selected_style=TAB_HIGHLIGHT_DICT, - children=[ - html.Div(id="Game pop general layout", - style=MAIN_PANEL_TAB_DICT, - className="scrollable", - children=[ - html.Div(id="Game pop top div", - children=[ - html.Div(id="game popularity filters", - style=NORMAL_DIVISION_DICT | {'width': '100%', - 'height': '100%', - 'margin_left': '0px', - 'margin_right': '0px', - 'background-color': TAB_COLOR, - 'display': 'inline-block'}, - - children=[ - html.P("Filters"), - html.Small("Number of game owners"), - dcc.RangeSlider(id=RATING_SLIDER, - min=min_owner, max=max_owner, - marks=owner_range_dict, - step=None, - value=[min_owner, - max_owner]), - html.Small("Minimum amount of reviews"), - dcc.Input(id=RATING_MIN_REVIEWS, - type="number", min=0, - max=max_reviews, step=1, value=0) - ] - ), - html.Div( - children=[dcc.Graph(id=RATING_DISTRIBUTION_PLOT)], - style=NORMAL_DIVISION_DICT | {'width': '100%', - 'display': 'inline-block'} - ) - ] - ), - html.Div(id="Game pop bottom_region", - style=NORMAL_DIVISION_DICT | {'width': '90%', - 'overflow': 'auto', - 'height': '70%'}, - className="scrollable", - children=[ - html.Div(dash.dash_table.DataTable(id=RATING_TABLE - )) - ]) - ] - ), - ] - ), - dcc.Tab(label="Market performance", value="tab4", - style=TAB_NORMAL_DICT, selected_style=TAB_HIGHLIGHT_DICT), - dcc.Tab(label="Market prediction tool", value="tab5", - style=TAB_NORMAL_DICT, selected_style=TAB_HIGHLIGHT_DICT), - ], - style=DEFAULT_TABS_DICT), - - ], style=PANEL_DEFAULT_DICT | {'width': '900px', - 'margin-right': '100px', 'padding-left': '50px', - 'padding-right': '50px', 'padding-bottom': '50px', - 'padding-top': '50px', 'margin-bottom': '50px' - }), - html.Div(children=[ - dcc.Tabs(id="company_information", value="tab3", children=[ - dcc.Tab(label="Developer infromation", value="tab3", children=[ - html.Div(children=[ - dcc.Dropdown(id=DEVELOPER_DROPDOWN, value="Valve", - options=[{"label": html.Span([developer], style={'color': WHITE_STEAM}), - "value": developer} for developer in unique_developers], - style={'margin-top': '20px', 'color': WHITE_STEAM}, - className='dash-dropdown', - ), - html.Div(children=[ - html.Div( - children=[ - html.Div( - children=[html.P("Revenue", style=SMALL_PANEL_HEADER_DICT)], - style={'margin-bottom': '10%', - 'border-bottom': '2px solid ' + TAB_EDGE}), - html.Div(children=[ - html.Div(children=[ - html.P("Game sale revenue estimates"), - html.Div(children=[ - html.Div(children=[ - html.P(id=DEV_REVENUE_LABEL, children="$524 M", - style=LIST_DICT | {'padding-left': '5%'}) - ]), - html.Div(children=[ - html.P(id=DEV_REV_PER_GAME_LABEL, children="$925 M", - style=LIST_DICT | {'padding-left': '5%'}) - ]), - ], - style={'margin-bottom': '20px'}), - html.Div(children=[ - html.P("Top games by revenue:"), - html.Small(id=DEV_TOP_GAMES, children="Half life 2"), - ]) - ]), - ], style={'padding-left': '5%', 'padding-right': '5%', - 'padding-bottom': '5%'}) - ], - style=SMALL_TAB_PANEL_DICT | {'margin-right': '20px', 'margin-left': '0px'} - ), - html.Div(children=[ - html.Div( - children=[ - html.Div( - children=[html.P("General information", - style=SMALL_PANEL_HEADER_DICT)], - style={'margin-bottom': '30px', - 'border-bottom': '2px solid ' + TAB_EDGE}), - html.Div(children=[ - html.Div(children=[ - html.P(id=DEV_GAME_COUNT_LABEL, children="5", - style=LIST_DICT), - ], - style={'margin-bottom': '10px'} - ), - html.Div(children=[ - html.P(id=DEV_CCU_LABEL, children="", - style=LIST_DICT), - ], - style={'margin-bottom': '10px'} - ), - html.Div(children=[ - html.P("Popular game genres:"), - html.Small(id=DEV_TOP_GENRES_LABEL, - children="FPS, Action, Puzzle"), - ], - style={'margin-bottom': '10px'} - ), - html.Div(children=[ - html.P(id=DEV_AVERAGE_RATING_LABEL, - children="", - style=LIST_DICT) - ]) - ]) - ]) - - ], style=SMALL_TAB_PANEL_DICT | {'width': '45%', 'height': '100%', - 'margin-right': '0px', 'margin-left': '20px'} - ) - ], style={'height': '100%'}) - ], style={'margin-left': '20px', 'margin-right': '0px'} - ) - ], style=TAB_NORMAL_DICT, selected_style=TAB_HIGHLIGHT_DICT), - dcc.Tab(label="Publisher information", value="tab4", children=[ - - dcc.Dropdown(id="publisher_dropdown", value="Valve", - options=[{"label": publisher, "value": publisher} for publisher in - unique_publishers], - ), - ], style=TAB_NORMAL_DICT, selected_style=TAB_HIGHLIGHT_DICT) - - ], - style=DEFAULT_TABS_DICT), - ], - style=PANEL_DEFAULT_DICT | {'width': '700px', - 'padding-left': '50px', - 'padding-right': '50px', 'padding-bottom': '50px', - 'padding-top': '50px', 'margin-bottom': '50px' - }) - ], - style={'width': '100%', "padding-top": "30px", 'padding-left': "50px"}), - ], - style={"font-family": "Tahoma"} - ) - - APP.run(host=host, **kwargs) - print("The server has closed!") - - -if __name__ == "__main__": - initialize_data() - initialize_dash(debug=True) - -print(csv_path) diff --git a/dash_plot_generation/data_store.py b/dash_plot_generation/data_store.py new file mode 100644 index 0000000..50a084a --- /dev/null +++ b/dash_plot_generation/data_store.py @@ -0,0 +1,38 @@ +import os +import pandas + +from dash_plot_generation.utils import convert_owners_to_limits, get_owner_means + +csv_path = os.path.normpath(os.getcwd() + os.sep + os.pardir + os.sep + "api_exploration") +split_csv_path = os.path.join(csv_path, "file_segments") + + +def initialize_data(): + global FULL_DATA, OWNER_RANGE_PARTS_SORTED + dataframe = None + for file in os.listdir(split_csv_path): + file_path = os.path.join(split_csv_path, file) + dataframe = pandas.concat([dataframe, pandas.read_csv(file_path)]) if dataframe is not None \ + else pandas.read_csv(file_path) + + add_game_revenues_and_owner_means(dataframe) + owner_ranges = {value_range for value_range in dataframe["owners"].unique()} + + ranges_test = [(convert_owners_to_limits(value_range), value_range.split(" .. ")) for value_range in owner_ranges] + unique_owner_values = {(limits[i], limits_str[i]) for (limits, limits_str) + in ranges_test for i in range(2)} + sorted_owner_list = sorted(unique_owner_values, key=lambda range: range[0]) + + OWNER_RANGE_PARTS_SORTED = sorted_owner_list + FULL_DATA = dataframe + return dataframe, sorted_owner_list + + +def add_game_revenues_and_owner_means(data): + data["owner_means"] = data["owners"].apply(lambda x: get_owner_means(convert_owners_to_limits(x))) + data["game_revenue"] = data.apply(lambda x: x["owner_means"] * x["price"] if + not (pandas.isna(x["owner_means"]) or pandas.isna(x["price"])) + else 0, axis=1) + + +FULL_DATA, OWNER_RANGE_PARTS_SORTED = initialize_data() diff --git a/dash_plot_generation/pages/__pycache__/about.cpython-311.pyc b/dash_plot_generation/pages/__pycache__/about.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..adec7b2db59a047318316c250003e7dea481c0c0 GIT binary patch literal 1117 zcmaJ<y=xRf6rZ`<`_9FWB%o+9AOr#?L9h^!1o1$HBnoFN!pbqXGufNVelat5WK*TE zu&}VRu(6P+Bv|GjxYi<p6vtKpZR{>2HmQ8G`M@AJyZd(DypP}Sy*Hn$)iQ$htvJa) zWDxpcl(SVh-5mae&3nWU!yZ~P|BQ!xyo52zAdcD)cd~evWw)#i&^OP|QXky5&U_#O zw{vG2%WYurhVNvh*8G{q^8d9KE?V6zD{hr8qAwNxVOVArHndgU07jeVtT5D8M-#)p z4D`YTPOO&py<<FWwh`5>werJe*wG}SE?@gn*PQxw+U>@qMHSI4PTY`sBwQx0=!(E4 zPUuH8h)KldawvToIGl(;3Ad#cp=l};rA#8?Q@O%5VN}zpdc++8sg^^hqli!@ArFWS z$+8F-;j57s3Qe0H1an7=FaR&GpC@yv&rdT>b>2fcf02cBlteOYLnQbxPMR^9173je zn1;7WolBPs@|*@#LRJch^K_NG?0phS_qK^6In|s2JDEa04YUkdC*@%{)}RDZcLX=} zI$=jDQ#N_RJtcw_K^6n3Mk=A>siXo0>d<dALl#4uOO8jCS^)M*sYX?+8B)pChUY27 zvvWN^oi=YGo8+|69#1NaD@TgRbV8bYHBn3~<1e$IBvr{>pw4B(OfD%l8UY|Q8cDV$ zx=H5A9r*4hd5^|0-LL2nV$km;5RdNLiwdaPK5f&k{Vd=$3Eb@$ac?{7ZO6Lz$xf#? zX~s5r+Em*hH(*4%;qrh>YG~O8TG=V_sVF`!KJcMu9-on80A%jI`VL}{#TfTdWj%fR zXl&==yN|A}TYKp0yL%sR?V#~pG`@$%VY6Sl@@DkywZ2tZuf2Z!W^UKIzGq!ORR;)H zZG3=m?iD^t<^GAEG0d3rS#vx)C>2IBgJCp$V{oZZzIlK^92v2mAB-YAk|Ou5p?-O+ Zf0^`4R|cgV&Kw{RM@9@T3OND={Q-@uKJWkl literal 0 HcmV?d00001 diff --git a/dash_plot_generation/pages/__pycache__/dash_plot_generator.cpython-311.pyc b/dash_plot_generation/pages/__pycache__/dash_plot_generator.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c5d8cf4cd3f1ceae68109aef27d31b3ad4697280 GIT binary patch literal 14146 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 literal 0 HcmV?d00001 diff --git a/dash_plot_generation/pages/__pycache__/home.cpython-311.pyc b/dash_plot_generation/pages/__pycache__/home.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bcd74e231a6abc0348aa9a610418920a59f5b153 GIT binary patch literal 643 zcmY*Vzl#(x7)@qof4jonDR^ixAS^6+CxV3>$2Bf!;hn{5;W9A9O?GGIGLs?64vZjd zVR40pot;?7iFoz-2kbwv2U{#tEm(=o$Z2zx$(|$Zm*mU$l9%s2^10cp0~OzX-XXc# zziUuL`CoDVK^5--1{hM%)4M@I2zw9$0}v1x#RFnu^Taw<`0M|(B?ksp!BH<@P%02E z9Ye)d>oS(Ng)YG5)!U(gD<{>}{=Eu@UC^%m&<zgSFta=v(hGQ3mobuFR_{uJ!Y)dZ z^wnO+7k!oxZcC5YgAt{6%p{4VkEqQCw$M}?BZ=$*<93MnA(1wU5KV<IwzEcmBqR&n zmkg&$pw_2IhyxUob~Upw5*{~|jjYMZ&{w^<8>1n~s;(O;j_YP-$4@fj@jbQfXJv{~ zHj-b!x%U3&g-Q>1oacgY;e;qaiSslf9quQ@d6rI2<H>0%C!d_9$1Uw~E9NX9ed%ar zH>OOwLlO~=q|YKpOAE(iA=!@8i|TI3@Q9K<zM*vVhl|%L<tBu14jSX-n1k!n)wuxc zV`~Q1-#z$vdkR`-pfv+6RV-?2N7vrom|Kl;=gp&|Cui2?%-URB!s!)-_h;6AUMX!D Zc^zE6nKw%HTfcxxzjYdy_26sD!Cz6suj&8* literal 0 HcmV?d00001 diff --git a/dash_plot_generation/pages/about.py b/dash_plot_generation/pages/about.py new file mode 100644 index 0000000..ed454f0 --- /dev/null +++ b/dash_plot_generation/pages/about.py @@ -0,0 +1,21 @@ +import dash +from dash import html + +dash.register_page(__name__, path='/') + +layout = html.Div([ + html.H1('About page'), + html.H4('SteamSavvy has the goal of giving company performance insights about the steam market data.' + 'Special focus is given to find exploitable sections in the steam game market.'), + html.H4('SteamSavvy is a project made by Linsen Gao, Sergei Panarin and Max Väistö created for the Introduction' + ' to Data Science course in Helsinki University.'), + +], className="custom_body") + + +dash.register_page( + __name__, + title="Dashboard", + description="Main dashboard", + path="/about", +) \ No newline at end of file diff --git a/dash_plot_generation/pages/dash_plot_generator.py b/dash_plot_generation/pages/dash_plot_generator.py new file mode 100644 index 0000000..a88940f --- /dev/null +++ b/dash_plot_generation/pages/dash_plot_generator.py @@ -0,0 +1,314 @@ +import numpy +import dash +from dash import html, dcc +from plotly import graph_objects as go + +from dash_plot_generation.styles_and_handles import RATING_MIN_REVIEWS, RATING_SLIDER, RATING_TABLE, \ + RATING_DISTRIBUTION_PLOT, MAIN_PANEL_TAB_DICT, DEV_AVERAGE_RATING_LABEL, \ + DEFAULT_PLOT_STYLE_DICT, WHITE_STEAM, TAB_COLOR, TAB_EDGE, DEFAULT_TABS_DICT, DEVELOPER_DROPDOWN, TAB_NORMAL_DICT, \ + TAB_HIGHLIGHT_DICT, PANEL_DEFAULT_DICT, SMALL_PANEL_DICT, SMALL_TAB_PANEL_DICT, SMALL_PANEL_HEADER_DICT, \ + DEV_TOP_GENRES_LABEL, LIST_DICT, NORMAL_DIVISION_DICT, DEV_CCU_LABEL, DEV_GAME_COUNT_LABEL, DEV_REV_PER_GAME_LABEL, \ + DEV_REVENUE_LABEL, DEV_TOP_GAMES, DARK_BLUE_STEAM + +from dash_plot_generation.data_store import FULL_DATA, OWNER_RANGE_PARTS_SORTED + +global APP + +# unique_publishers = extract_unique_companies(df["publisher"].apply(lambda x: split_companies(x))) +# unique_developers = extract_unique_companies(df["developer"].iloc[0:10].apply(lambda x: split_companies(x))) +unique_publishers = ["Valve"] +unique_developers = ["Valve"] + +# Genre performance table_values +# genre_owners, genre_revenue = get_genre_popularity_counts(FULL_DATA, 6) +genre_owners = {key: val for (key, val) in + zip(["Action", "Adventure", "RPG", "Puzzle", "Strategy", "Other"], + [0.7, 0.5, 0.1, 0.4, 0.3, 0.7])} +genre_revenue = {key: val for (key, val) in + zip(["Action", "Adventure", "RPG", "Puzzle", "Strategy", "Other"], + [0.5, 0.4, 0.3, 0.4, 0.6, 0.7])} + +# Game popularity filter values +max_reviews = numpy.nanmax(FULL_DATA.apply(lambda x: x["positive"] + x["negative"], axis=1)) +owner_range_dict = {index: val_str for (index, (val, val_str)) in enumerate(OWNER_RANGE_PARTS_SORTED)} +min_owner = min(owner_range_dict.keys()) +max_owner = max(owner_range_dict.keys()) + + +layout = html.Div( + children=[ + html.Div(className="row", children=[ + html.Div(children=[ + dcc.Tabs(id="main_plots", value="tab1", children=[ + dcc.Tab(label="Genre performance", value="tab1", + style=TAB_NORMAL_DICT, selected_style=TAB_HIGHLIGHT_DICT, + children=[ + html.Div(children=[ + html.Div( + children=[ + html.Div(style=NORMAL_DIVISION_DICT, + children=[dcc.Graph( + figure=go.Figure(data=[ + {'x': ["Action", "Adventure", "RPG", "Puzzle", + "Strategy"], + 'y': [0.7, 0.4, 0.8, 1.2, 1.3], + 'type': 'bar'}, + ], + layout=DEFAULT_PLOT_STYLE_DICT | + dict(title="Relative genre perfomance") + ), + + ), + html.P(f"""Genre performance measures the assessed + exploitability of the specific game genre. The assessment + is done by estimating the genre popularity, and games + developed in the next two years and showing the relative + differences between the genres.""")]), + html.Div(style=SMALL_PANEL_DICT | {'width': '35%', 'height': '100%', + 'background-color': TAB_COLOR}, + children=[ + html.Div(children=[ + html.Div(style={'width': '100%', 'height': '50%'}, + children=[dcc.Graph( + figure=go.Figure(data=[go.Pie( + labels=list(genre_owners.keys()), + values=list(genre_owners.values()), + sort=False)], + layout=DEFAULT_PLOT_STYLE_DICT | + dict(title="Genre popularity", + margin=dict(l=20, r=20, + t=50, b=20))), + style={'width': '100%', + 'height': '100%'})]), + html.Div(style={'width': '100%', 'height': '50%'}, + children=[dcc.Graph( + figure=go.Figure(data=[go.Pie( + labels=list(genre_revenue.keys()), + values=list(genre_revenue.values()), + sort=False)], + layout=DEFAULT_PLOT_STYLE_DICT | + dict( + title="Genre revenue share", + margin=dict(l=20, r=20, + t=50, b=20))), + style={'width': '100%', + 'height': '100%'})] + ) + + ], style={'height': '540px'} + ), + ] + ) + ] + ), + + html.Div(children=[ + html.H5("Genre prediction", style={'margin-bottom': '50px'}), + html.Div(children=[ + html.Div(children=[ + html.P("Selected genre:", style={'margin-bottom': '10px'}), + dcc.Dropdown(id="genre_dropdown", value="action", + options=[{"label": html.Span([genre], + style={ + 'color': WHITE_STEAM}), + "value": genre} for genre in + ["action"]], + style={'color': WHITE_STEAM, 'display': 'inline-block', + 'width': '50%'}, + className='dash-dropdown', + ), + + ], + style={'width': '100%', 'margin-bottom': '50px'}) + ]), + dcc.Graph(figure=go.Figure(layout=DEFAULT_PLOT_STYLE_DICT | + dict(title="Genre prediction plot", + margin=dict(l=20, r=20, + t=50, b=20)))), + html.P("""This is an individual regression estimate for the genre that represents + the estimated amount of games to be produced in the next two years""") + + ], style=NORMAL_DIVISION_DICT | {'width': '90%'}) + + ], + style=MAIN_PANEL_TAB_DICT, + className="scrollable") + ]), + dcc.Tab(label="Game popularity", value="tab2", + style=TAB_NORMAL_DICT, selected_style=TAB_HIGHLIGHT_DICT, + children=[ + html.Div(id="Game pop general layout", + style=MAIN_PANEL_TAB_DICT, + className="scrollable", + children=[ + html.Div(id="Game pop top div", + children=[ + html.Div(id="game popularity filters", + style=NORMAL_DIVISION_DICT | {'width': '100%', + 'height': '100%', + 'margin_left': '0px', + 'margin_right': '0px', + 'background-color': TAB_COLOR, + 'display': 'inline-block'}, + + children=[ + html.P("Filters"), + html.Small("Number of game owners"), + dcc.RangeSlider(id=RATING_SLIDER, + min=min_owner, max=max_owner, + marks=owner_range_dict, + step=None, + value=[min_owner, + max_owner]), + html.Small("Minimum amount of reviews"), + dcc.Input(id=RATING_MIN_REVIEWS, + type="number", min=0, + max=max_reviews, step=1, value=0) + ] + ), + html.Div( + children=[dcc.Graph(id=RATING_DISTRIBUTION_PLOT)], + style=NORMAL_DIVISION_DICT | {'width': '100%', + 'display': 'inline-block'} + ) + ] + ), + html.Div(id="Game pop bottom_region", + style=NORMAL_DIVISION_DICT | {'width': '90%', + 'overflow': 'auto', + 'height': '70%'}, + className="scrollable", + children=[ + html.Div(dash.dash_table.DataTable(id=RATING_TABLE + )) + ]) + ] + ), + ] + ), + dcc.Tab(label="Market performance", value="tab4", + style=TAB_NORMAL_DICT, selected_style=TAB_HIGHLIGHT_DICT), + dcc.Tab(label="Market prediction tool", value="tab5", + style=TAB_NORMAL_DICT, selected_style=TAB_HIGHLIGHT_DICT), + ], + style=DEFAULT_TABS_DICT), + + ], style=PANEL_DEFAULT_DICT | {'width': '900px', + 'margin-right': '100px', 'padding-left': '50px', + 'padding-right': '50px', 'padding-bottom': '50px', + 'padding-top': '50px', 'margin-bottom': '50px' + }), + html.Div(children=[ + dcc.Tabs(id="company_information", value="tab3", children=[ + dcc.Tab(label="Developer infromation", value="tab3", children=[ + html.Div(children=[ + dcc.Dropdown(id=DEVELOPER_DROPDOWN, value="Valve", + options=[{"label": html.Span([developer], style={'color': WHITE_STEAM}), + "value": developer} for developer in unique_developers], + style={'margin-top': '20px', 'color': WHITE_STEAM}, + className='dash-dropdown', + ), + html.Div(children=[ + html.Div( + children=[ + html.Div( + children=[html.P("Revenue", style=SMALL_PANEL_HEADER_DICT)], + style={'margin-bottom': '10%', + 'border-bottom': '2px solid ' + TAB_EDGE}), + html.Div(children=[ + html.Div(children=[ + html.P("Game sale revenue estimates"), + html.Div(children=[ + html.Div(children=[ + html.P(id=DEV_REVENUE_LABEL, children="$524 M", + style=LIST_DICT | {'padding-left': '5%'}) + ]), + html.Div(children=[ + html.P(id=DEV_REV_PER_GAME_LABEL, children="$925 M", + style=LIST_DICT | {'padding-left': '5%'}) + ]), + ], + style={'margin-bottom': '20px'}), + html.Div(children=[ + html.P("Top games by revenue:"), + html.Small(id=DEV_TOP_GAMES, children="Half life 2"), + ]) + ]), + ], style={'padding-left': '5%', 'padding-right': '5%', + 'padding-bottom': '5%'}) + ], + style=SMALL_TAB_PANEL_DICT | {'margin-right': '20px', 'margin-left': '0px'} + ), + html.Div(children=[ + html.Div( + children=[ + html.Div( + children=[html.P("General information", + style=SMALL_PANEL_HEADER_DICT)], + style={'margin-bottom': '30px', + 'border-bottom': '2px solid ' + TAB_EDGE}), + html.Div(children=[ + html.Div(children=[ + html.P(id=DEV_GAME_COUNT_LABEL, children="5", + style=LIST_DICT), + ], + style={'margin-bottom': '10px'} + ), + html.Div(children=[ + html.P(id=DEV_CCU_LABEL, children="", + style=LIST_DICT), + ], + style={'margin-bottom': '10px'} + ), + html.Div(children=[ + html.P("Popular game genres:"), + html.Small(id=DEV_TOP_GENRES_LABEL, + children="FPS, Action, Puzzle"), + ], + style={'margin-bottom': '10px'} + ), + html.Div(children=[ + html.P(id=DEV_AVERAGE_RATING_LABEL, + children="", + style=LIST_DICT) + ]) + ]) + ]) + + ], style=SMALL_TAB_PANEL_DICT | {'width': '45%', 'height': '100%', + 'margin-right': '0px', 'margin-left': '20px'} + ) + ], style={'height': '100%'}) + ], style={'margin-left': '20px', 'margin-right': '0px'} + ) + ], style=TAB_NORMAL_DICT, selected_style=TAB_HIGHLIGHT_DICT), + dcc.Tab(label="Publisher information", value="tab4", children=[ + + dcc.Dropdown(id="publisher_dropdown", value="Valve", + options=[{"label": publisher, "value": publisher} for publisher in + unique_publishers], + ), + ], style=TAB_NORMAL_DICT, selected_style=TAB_HIGHLIGHT_DICT) + + ], + style=DEFAULT_TABS_DICT), + ], + style=PANEL_DEFAULT_DICT | {'width': '700px', + 'padding-left': '50px', + 'padding-right': '50px', 'padding-bottom': '50px', + 'padding-top': '50px', 'margin-bottom': '50px' + }) + ], + style={'width': '100%', "padding-top": "30px", 'padding-left': "50px"}), + ], + style={"font-family": "Tahoma"}, + className="body" +) + +dash.register_page( + __name__, + path_template="/stocks/<ticker>", + title="Dashboard", + description="Main dashboard", + path="/dashboard", +) \ No newline at end of file diff --git a/dash_plot_generation/pages/home.py b/dash_plot_generation/pages/home.py new file mode 100644 index 0000000..bf74e8b --- /dev/null +++ b/dash_plot_generation/pages/home.py @@ -0,0 +1,11 @@ +import dash +from dash import html + +dash.register_page(__name__, path='/') + +layout = html.Div([ + html.H1('SteamSavvy'), + + html.H4('Discover the full potential of steam data for market analysis.'), + +], className="custom_body") \ No newline at end of file diff --git a/dash_plot_generation/pages/technical_report.py b/dash_plot_generation/pages/technical_report.py new file mode 100644 index 0000000..e69de29 diff --git a/dash_plot_generation/styles_and_handles.py b/dash_plot_generation/styles_and_handles.py new file mode 100644 index 0000000..354a4ae --- /dev/null +++ b/dash_plot_generation/styles_and_handles.py @@ -0,0 +1,72 @@ + +# html handles +RATING_MIN_REVIEWS = "min_reviews_id" +RATING_SLIDER = "rating_slider" +RATING_TABLE = "rating_data_table" +DEV_CCU_LABEL = "dev_ccu" +DEV_GAME_COUNT_LABEL = "dev_game_count" +DEV_REV_PER_GAME_LABEL = "dev_rev_per_game" +DEV_REVENUE_LABEL = "dev_revenue" +DEV_TOP_GAMES = "pub_top_games" +PUB_TOP_GENRES_LABEL = "pub_top_genres" +PUB_CCU_LABEL = "pub_ccu" +PUB_GAME_COUNT_LABEL = "pub_game_count" +PUB_REV_PER_GAME_LABEL = "pub_rev_per_game" +PUB_REVENUE_LABEL = "pub_revenue" +PUB_TOP_GAMES = "pub_top_games" +DEV_TOP_GENRES_LABEL = "dev_top_genres" +DEVELOPER_DROPDOWN = "developer_dropdown" +RATING_DISTRIBUTION_PLOT = "game_popularity_density_plot" +DEV_AVERAGE_RATING_LABEL = "dev_average_rating" + + +MAIN_PANEL_TAB_DICT = {'height': '550px', 'width': '100%', 'margin': '0', 'overflow': 'auto'} +SPACE_NORMAL_ENTRY = 35 +RIGHT_SIDE_TEXT_DICT = {'display': 'inline-block', + 'float': 'right', 'margin-right': '0%', 'margin-bottom': '0px'} + +DEFAULT_PLOT_STYLE_DICT = dict( + template="plotly_dark", + plot_bgcolor="rgb(31,46,65)", + paper_bgcolor="rgb(31,46,65)", +) + +DENSITY_LAYOUT_STYLE = DEFAULT_PLOT_STYLE_DICT | dict( + title='Distribution of Game Review Rating', + xaxis_title="Game user rating", + yaxis_title="Proportion" + +) +DARK_STEAM = "rgb(23,29,37)" +WHITE_STEAM = "rgb(235,235,235)" +TITLE_WHITE_STEAM = "rgb(197,195,192)" +DARK_BLUE_STEAM = "rgb(27,40,56)" +TAB_COLOR = "rgb(31,46,65)" +TAB_EDGE = "rgb(37,55,77)" +DROPDOWN_COLOR = "rgb(50,70,101" +SMALL_PANEL_COLOR = "rgb(22,32,45)" +TAB_HEADER_COLOR = "rgb(45,96,150)" + +DEFAULT_TABS_DICT = {'width': 'auto', 'display': 'flex', + 'background-color': TAB_COLOR, 'border-color': TAB_EDGE} +TAB_NORMAL_DICT = {'background-color': TAB_COLOR, 'color': TITLE_WHITE_STEAM, + 'border': '0px solid', + 'font-size': '15px', + 'border_bottom': '2px solid ' + TAB_EDGE} +TAB_HIGHLIGHT_DICT = {'backgroundColor': TAB_HEADER_COLOR, 'color': 'white', "border-color": "transparent", + 'font-size': '15px'} +PANEL_DEFAULT_DICT = {'display': 'inline-block', + 'background-color': TAB_COLOR, 'border': '2px solid', 'border-color': TAB_EDGE, + 'color': WHITE_STEAM, 'height': '600px'} +SMALL_PANEL_DICT = {'float': 'left', 'background-color': 'transparent', 'box-sizing': 'border-box', + 'padding': '10px'} +SMALL_TAB_PANEL_DICT = SMALL_PANEL_DICT | {'width': '48%', 'height': '100%', + 'margin-bottom': '50px', + 'padding-top': '4%', 'padding-bottom': '5%', 'padding-left': '5%', + 'padding-right': '5%', + 'margin-top': '20px' + } +SMALL_PANEL_HEADER_DICT = {'text-align': 'center', 'padding-top': '5%', 'padding-bottom': '2%'} +LIST_DICT = {'display': 'inline-block', 'margin-bottom': '0px', 'padding-right': '0px'} +NORMAL_DIVISION_DICT = SMALL_PANEL_DICT | {'width': '60%', 'height': '100%', 'margin-right': '5%', 'padding-left': '5%', + 'margin-bottom': '5%', 'background-color': TAB_COLOR} diff --git a/dash_plot_generation/utils.py b/dash_plot_generation/utils.py index 6d380af..4fd831a 100644 --- a/dash_plot_generation/utils.py +++ b/dash_plot_generation/utils.py @@ -1,7 +1,13 @@ import math import re +from collections import Counter from typing import Sequence, Optional, Any + +import numpy import pandas +from dash import html + +from dash_plot_generation.styles_and_handles import SPACE_NORMAL_ENTRY DEFAULT_ILLEGAL_CONTINUATIONS = {"INC.", "LLC", "CO.", "LTD.", "S.R.O."} @@ -120,3 +126,79 @@ def round_to_three_largest_digits(number, accuracy=2): round_val = -(len(str(round(number))) - accuracy) return_val = round(round(number), min(round_val, 0)) return return_val + + +def get_average_user_rating_label(dev_data): + value_str = str(round(100 * dev_data["Review_rating"].mean())) + "%" + label = label_with_text("Average game rating", value_str, SPACE_NORMAL_ENTRY, ".") + return label + + +def get_game_count_label(dev_data): + return label_with_text("Number of games", str(dev_data.shape[0]), SPACE_NORMAL_ENTRY, ".") + + +def get_top_revenue_game_labels(data): + top_games = data.sort_values(by=["game_revenue"], ascending=False).head(3) + top_games_processed = top_games.apply(lambda x: label_with_rev(x["name"], x["game_revenue"], SPACE_NORMAL_ENTRY, + ".", "$"), axis=1) + dev_top_games_with_dot = [" ".join(["•", game]) for game in top_games_processed] + dev_top_games_label = html.Div("\n".join(dev_top_games_with_dot), + style={'white-space': 'pre-line', 'padding-left': '5%'}) + return dev_top_games_label + + +def get_total_revenue_label(data): + top_games_processed = label_with_rev("• Total", numpy.nansum(data["game_revenue"]), SPACE_NORMAL_ENTRY, ".", "$") + return top_games_processed + + +def get_top_genre_labels(data): + genre_totals = [genre for genre_list in data["genres"] if isinstance(genre_list, str) + for genre in genre_list.split(", ")] + genre_counts = Counter(genre_totals).most_common(3) + top_genres_rows = [label_with_text(genre[0], str(genre[1]), 50, ".") for genre in genre_counts] + top_genres_with_dot = [" ".join(["•", row]) for row in top_genres_rows] + top_genre_labels = html.Div("\n".join(top_genres_with_dot), + style={'white-space': 'pre-line', 'padding-left': '5%'}) + return top_genre_labels + + +def get_ccu_label(data): + ccu = sum(data["ccu"]) + dev_ccu = convert_to_numeric_str(ccu) + + return label_with_text("Concurrent users", dev_ccu, SPACE_NORMAL_ENTRY, ".") + + +def get_genre_popularity_counts(df, group_after_largest=8): + genre_df = df[["genres", "owner_means", "game_revenue"]] + genre_owners = {} + genre_revenue = {} + + for index, row in genre_df.iterrows(): + if not isinstance(row.genres, str): + continue + genre_list = row.genres.split(", ") + for genre in genre_list: + if genre in genre_owners.keys(): + genre_owners[genre] += row["owner_means"] + genre_revenue[genre] += row["game_revenue"] + else: + genre_owners[genre] = row["owner_means"] + genre_revenue[genre] = row["game_revenue"] + top_owners = dict(Counter(genre_owners).most_common(group_after_largest)) + top_revenue = dict(Counter(genre_revenue).most_common(group_after_largest)) + top_owners["Other"] = sum([val for (key, val) in genre_owners.items() + if key not in top_owners.keys()]) + top_revenue["Other"] = sum([val for (key, val) in genre_revenue.items() + if key not in top_revenue.keys()]) + + return top_owners, top_revenue + + +def get_average_game_rev_label(data): + game_revenue_per_game_raw = numpy.nansum(data["game_revenue"]) / len(data["game_revenue"]) + dev_game_revenue_per_game_row = label_with_rev("Average", game_revenue_per_game_raw, SPACE_NORMAL_ENTRY, ".", "$") + dev_game_revenue_per_game = " ".join(["•", dev_game_revenue_per_game_row]) + return dev_game_revenue_per_game -- GitLab