From b077ef4ae588ed1f9abf99d273c55a05fca725c8 Mon Sep 17 00:00:00 2001 From: Florian Bezannier Date: Wed, 21 Apr 2021 17:23:43 +0200 Subject: [PATCH] enable access to control tab when no data & improve style --- web/figures.py | 74 ++++++++++------------- web/views.py | 159 ++++++++++++++++++++++++++----------------------- 2 files changed, 117 insertions(+), 116 deletions(-) diff --git a/web/figures.py b/web/figures.py index 9177a98..0f005a2 100644 --- a/web/figures.py +++ b/web/figures.py @@ -46,15 +46,17 @@ def get_marks_from_start_end(start, end): # pylint: disable=invalid-name -consumption_fig = None -consumption_df = None -trips_map = None -consumption_fig_by_speed = None -consumption_graph_by_temp = None -table_fig = None +ERROR_DIV = dbc.Alert("No data to show, there is probably no trips recorded yet", color="danger") +PADDING_TOP = {"padding-top": "1em"} +consumption_fig = ERROR_DIV +consumption_df = ERROR_DIV +trips_map = ERROR_DIV +consumption_fig_by_speed = ERROR_DIV +consumption_graph_by_temp = ERROR_DIV +table_fig = ERROR_DIV pandas_options.display.float_format = '${:.2f}'.format info = "" -battery_info = dbc.Alert("No data to show", color="danger") +battery_info = ERROR_DIV battery_table = None @@ -130,42 +132,32 @@ def get_figures(trips: Trips, charging: List[dict]): html.Tr( [ html.Td('Average emission:', rowSpan=2), - html.Td("{:.1f} g/km".format(co2_per_km)), - ] - ), + html.Td("{:.1f} g/km".format(co2_per_km))]), html.Tr( [ "{:.1f} g/kWh".format(co2_per_kw), ] ), - html.Tr( - [ - html.Td("Average charge speed:"), - html.Td("{:.3f} kW".format(charge_speed)) - ] - ), - html.Tr( - [ - html.Td('Average Price:', rowSpan=2), - html.Td("{:.2f} {}/100km".format(price_kw * kw_per_km, ElecPrice.currency)), - ] - ), - html.Tr( - [ - "{:.2f} {}/kWh".format(price_kw, ElecPrice.currency), - ] - ), - html.Tr( - [ - html.Td('Electricity consumption:', rowSpan=2), - html.Td("{:.0f} kWh".format(total_elec)), - ] - ), - html.Tr( - [ - "{:.0f} {}".format(total_elec*price_kw, ElecPrice.currency), - ] - ), + html.Tr([ + html.Td("Average charge speed:", style=PADDING_TOP), + html.Td("{:.3f} kW".format(charge_speed)) + ]), + html.Tr(html.Td(" ", colSpan=2)), + html.Tr([ + html.Td('Average Price:', rowSpan=2, style=PADDING_TOP), + html.Td("{:.2f} {}/100km".format(price_kw * kw_per_km, ElecPrice.currency)), + ]), + html.Tr([ + "{:.2f} {}/kWh".format(price_kw, ElecPrice.currency), + ]), + html.Tr(html.Td(" ", colSpan=2)), + html.Tr([ + html.Td('Electricity consumption:', rowSpan=2, style=PADDING_TOP), + html.Td("{:.0f} kWh".format(total_elec)), + ]), + html.Tr([ + "{:.0f} {}".format(total_elec * price_kw, ElecPrice.currency), + ]), ]) battery_table = dash_table.DataTable( @@ -181,9 +173,9 @@ def get_figures(trips: Trips, charging: List[dict]): {'id': 'kw', 'name': 'consumption', 'type': 'numeric', 'format': deepcopy(nb_format).symbol_suffix(" kWh").precision(2)}, {'id': 'price', 'name': 'price', 'type': 'numeric', - 'format': deepcopy(nb_format).symbol_suffix(" " + ElecPrice.currency).precision(2)}], - data=charging, - editable=True + 'format': deepcopy(nb_format).symbol_suffix(" " + ElecPrice.currency).precision(2), 'editable': True} + ], + data=charging ) consumption_by_temp_df = consumption_df[consumption_df["consumption_by_temp"].notnull()] if len(consumption_by_temp_df) > 0: diff --git a/web/views.py b/web/views.py index b5cf414..6b7270e 100644 --- a/web/views.py +++ b/web/views.py @@ -27,8 +27,8 @@ from web.db import set_chargings_price, get_db, set_db_callback RESPONSE = "-response" EMPTY_DIV = "empty-div" ABRP_SWITCH = 'abrp-switch' +CALLBACK_CREATED = False -ERROR_DIV = dbc.Alert("No data to show, there is probably no trips recorded yet", color="danger") trips: Trips chargings: List[dict] min_date = max_date = min_millis = max_millis = step = marks = cached_layout = None @@ -57,50 +57,56 @@ def diff_dashtable(data, data_previous, row_id_name="row_id"): return changes -@dash_app.callback(Output('trips_map', 'figure'), - Output('consumption_fig', 'figure'), - Output('consumption_fig_by_speed', 'figure'), - Output('consumption_graph_by_temp', 'children'), - Output('consumption', 'children'), - Output('tab_trips', 'children'), - Output('tab_battery', 'children'), - Output('tab_charge', 'children'), - Output('date-slider', 'max'), - Output('date-slider', 'step'), - Output('date-slider', 'marks'), - Input('date-slider', 'value')) -def display_value(value): - mini = datetime.fromtimestamp(value[0], tz=timezone.utc) - maxi = datetime.fromtimestamp(value[1], tz=timezone.utc) - filtered_trips = Trips() - for trip in trips: - if mini <= trip.start_at <= maxi: - filtered_trips.append(trip) - filtered_chargings = Charging.get_chargings(mini, maxi) - figures.get_figures(filtered_trips, filtered_chargings) - consumption = "Average consumption: {:.1f} kWh/100km".format(float(figures.consumption_df["consumption_km"].mean())) - return figures.trips_map, figures.consumption_fig, figures.consumption_fig_by_speed, \ - figures.consumption_graph_by_temp, consumption, figures.table_fig, figures.battery_info, \ - figures.battery_table, max_millis, step, marks +def create_callback(): + global CALLBACK_CREATED + if not CALLBACK_CREATED: + # pylint: disable=unused-variable + @dash_app.callback(Output('trips_map', 'figure'), + Output('consumption_fig', 'figure'), + Output('consumption_fig_by_speed', 'figure'), + Output('consumption_graph_by_temp', 'children'), + Output('consumption', 'children'), + Output('tab_trips', 'children'), + Output('tab_battery', 'children'), + Output('tab_charge', 'children'), + Output('date-slider', 'max'), + Output('date-slider', 'step'), + Output('date-slider', 'marks'), + Input('date-slider', 'value')) + def display_value(value): + mini = datetime.fromtimestamp(value[0], tz=timezone.utc) + maxi = datetime.fromtimestamp(value[1], tz=timezone.utc) + filtered_trips = Trips() + for trip in trips: + if mini <= trip.start_at <= maxi: + filtered_trips.append(trip) + filtered_chargings = Charging.get_chargings(mini, maxi) + figures.get_figures(filtered_trips, filtered_chargings) + consumption = "Average consumption: {:.1f} kWh/100km".format( + float(figures.consumption_df["consumption_km"].mean())) + return figures.trips_map, figures.consumption_fig, figures.consumption_fig_by_speed, \ + figures.consumption_graph_by_temp, consumption, figures.table_fig, figures.battery_info, \ + figures.battery_table, max_millis, step, marks + @dash_app.callback( + Output(EMPTY_DIV, "children"), + [Input("battery-table", "data_timestamp")], + [ + State("battery-table", "data"), + State("battery-table", "data_previous"), + ], + ) + def capture_diffs(timestamp, data, data_previous): + if timestamp is None: + raise PreventUpdate + diff_data = diff_dashtable(data, data_previous, "start_at") + for changed_line in diff_data: + if changed_line['column_name'] == 'price': + if not set_chargings_price(get_db(), changed_line['start_at'], changed_line['current_value']): + logger.error("Can't find line to update in the database") + return "" -@dash_app.callback( - Output(EMPTY_DIV, "children"), - [Input("battery-table", "data_timestamp")], - [ - State("battery-table", "data"), - State("battery-table", "data_previous"), - ], -) -def capture_diffs(timestamp, data, data_previous): - if timestamp is None: - raise PreventUpdate - diff_data = diff_dashtable(data, data_previous, "start_at") - for changed_line in diff_data: - if changed_line['column_name'] == 'price': - if not set_chargings_price(get_db(), changed_line['start_at'], changed_line['current_value']): - logger.error("Can't find line to update in the database") - return "" + CALLBACK_CREATED = True @dash_app.callback(Output({'role': ABRP_SWITCH + RESPONSE, 'vin': MATCH}, 'children'), @@ -267,41 +273,44 @@ def serve_layout(): logger.debug("Create new layout") try: figures.get_figures(trips, chargings) - data_div = html.Div([ - dcc.RangeSlider( - id='date-slider', - min=min_millis, - max=max_millis, - step=step, - marks=marks, - value=[min_millis, max_millis], - ), - html.Div([ - dbc.Tabs([ - dbc.Tab(label="Summary", tab_id="summary", - children=[ - html.H2(id="consumption", - children=figures.info), - dcc.Graph(figure=figures.consumption_fig, id="consumption_fig"), - dcc.Graph(figure=figures.consumption_fig_by_speed, id="consumption_fig_by_speed"), - figures.consumption_graph_by_temp - ]), - dbc.Tab(label="Trips", tab_id="trips", id="tab_trips", children=[figures.table_fig]), - dbc.Tab(label="Battery", tab_id="battery", id="tab_battery", children=[figures.battery_info]), - dbc.Tab(label="Charge", tab_id="charge", id="tab_charge", children=[figures.battery_table]), - dbc.Tab(label="Map", tab_id="map", children=[ - dcc.Graph(figure=figures.trips_map, id="trips_map", style={"height": '90vh'})]), - dbc.Tab(label="Control", tab_id="control", children=dbc.Tabs(id="control-tabs", - children=__get_control_tabs())) - ], - id="tabs", - active_tab="summary"), - html.Div(id=EMPTY_DIV), - ])]) + summary_tab = [html.H2(id="consumption", + children=figures.info), + dcc.Graph(figure=figures.consumption_fig, id="consumption_fig"), + dcc.Graph(figure=figures.consumption_fig_by_speed, id="consumption_fig_by_speed"), + figures.consumption_graph_by_temp] + maps = dcc.Graph(figure=figures.trips_map, id="trips_map", style={"height": '90vh'}) + create_callback() + range_slider = dcc.RangeSlider( + id='date-slider', + min=min_millis, + max=max_millis, + step=step, + marks=marks, + value=[min_millis, max_millis], + ) except (IndexError, TypeError, NameError): + summary_tab = figures.ERROR_DIV + maps = figures.ERROR_DIV logger.warning("Failed to generate figure, there is probably not enough data yet") logger.debug(traceback.format_exc()) - data_div = ERROR_DIV + range_slider = html.Div() + data_div = html.Div([ + range_slider, + html.Div([ + dbc.Tabs([ + dbc.Tab(label="Summary", tab_id="summary", children=summary_tab), + dbc.Tab(label="Trips", tab_id="trips", id="tab_trips", children=[figures.table_fig]), + dbc.Tab(label="Battery", tab_id="battery", id="tab_battery", children=[figures.battery_info]), + dbc.Tab(label="Charge", tab_id="charge", id="tab_charge", children=[figures.battery_table]), + dbc.Tab(label="Map", tab_id="map", children=[maps]), + dbc.Tab(label="Control", tab_id="control", children=dbc.Tabs(id="control-tabs", + children=__get_control_tabs())) + ], + id="tabs", + active_tab="summary", + persistence=True), + html.Div(id=EMPTY_DIV), + ])]) cached_layout = dbc.Container(fluid=True, children=[html.H1('My car info'), data_div]) return cached_layout