WIP client filter

This commit is contained in:
Florian Bezannier
2021-05-10 20:53:53 +02:00
parent a0fcba1dee
commit c31b3f4854
10 changed files with 165 additions and 53 deletions
+10 -2
View File
@@ -89,8 +89,8 @@ class Trip:
return res
def get_consumption(self):
return {"speed": self.speed_average, "consumption_km": self.consumption_km, "date": self.start_at,
"consumption_by_temp": self.get_temperature()}
return {"speed": self.speed_average, "consumption_km": self.consumption_km, "start_at": self.start_at,
"consumption_by_temp": self.get_temperature(), "positions": self.get_positions()}
def set_altitude_diff(self, start, end):
try:
@@ -98,6 +98,14 @@ class Trip:
except (NameError, TypeError):
pass
def get_positions(self):
lat = []
long = []
for position in self.positions:
lat.append(position.latitude)
long.append(position.longitude)
return {"lat": lat, "long": long}
class Trips(list):
def __init__(self, *args):
+5 -1
View File
@@ -27,7 +27,8 @@ myp: MyPSACC = None
chc: ChargeControls = None
def start_app(title, base_path, debug: bool, host, port, reloader=False): # pylint: disable=too-many-arguments
def start_app(title, base_path, debug: bool, host, port, reloader=False, # pylint: disable=too-many-arguments
unminified=False):
global app, dash_app, dispatcher
try:
lang = locale.getlocale()[0].split("_")[0]
@@ -36,6 +37,8 @@ def start_app(title, base_path, debug: bool, host, port, reloader=False): # pyl
except (IndexError, locale.Error):
locale_url = None
logger.warning("Can't get language")
if unminified:
locale_url = ["assets/plotly-with-meta.js"]
app = Flask(__name__)
app.config["DEBUG"] = debug
if base_path == "/":
@@ -46,6 +49,7 @@ def start_app(title, base_path, debug: bool, host, port, reloader=False): # pyl
requests_pathname_prefix = base_path + "/"
dash_app = dash.Dash(external_stylesheets=[dbc.themes.BOOTSTRAP], external_scripts=locale_url, title=title,
server=app, requests_pathname_prefix=requests_pathname_prefix)
dash_app.enable_dev_tools(reloader)
# keep this line
import web.views # pylint: disable=unused-import,import-outside-toplevel
return run_simple(host, port, application, use_reloader=reloader, use_debugger=debug)
+48
View File
@@ -0,0 +1,48 @@
function filter_dataset(data, range, old_figure, x,y) {
function is_in_range(st){
ts_date = new Date(st).getTime()/1000
return ts_date >= range[0] && ts_date <= range[1]
}
var data_filtered = data.filter(line => is_in_range(line["start_at"]));
var figures = [];
var i=0
y.forEach(function (y_label){
var x_label=x[i]
var figure = Object.assign({}, old_figure[i]);
i++;
// var unique_y_label = y[i].filter((v, i, a) => a.indexOf(v) === i);
//var data_nonnull = data_filtered
// unique_y_label.forEach(function(label) {
// data_nonnull = data_nonnull.filter(line => line[label]);
// });
if ("mapbox" in figure["layout"]){
figure["data"][0]["lat"] = []
figure["data"][0]["lon"] = []
figure["data"][0]["hovertext"] = []
var trip = null;
for (trip of data_filtered) {
x_pos = trip["positions"][x_label]
figure["data"][0]["lat"].push(...x_pos, null);
figure["data"][0]["lon"].push(...trip["positions"][y_label[0]]);
figure["data"][0]["hovertext"].push(...Array(x_pos.length).fill(trip[y_label[1]]), null);
}
var last_pos = trip["positions"][y_label[0]].length;
figure.layout.mapbox.center.lat = trip["positions"][x_label][last_pos - 1];
figure.layout.mapbox.center.lon = trip["positions"][y_label[0]][last_pos - 1];
figure.data[1].lat = [figure.layout.mapbox.center.lat]
figure.data[1].lon = [figure.layout.mapbox.center.lon]
}
else {
x_values = data_filtered.map(a => a[x_label])
// for each y label
for (j = 0; j < y_label.length; j++) {
figure["data"][j]["y"] = data_filtered.map(a => a[y_label[j]]);
figure["data"][j]["x"] = x_values
}
}
console.log(figure);
figures.push(figure);
});
return figures;
}

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

+22
View File
@@ -0,0 +1,22 @@
{
"version": 8,
"sources": {
"osm": {
"type": "raster",
"tiles": [
"https://tile.openstreetmap.org/{z}/{x}/{y}.png"
],
"tileSize": 256,
"attribution": "Map tiles by <a target=\"_top\" rel=\"noopener\" href=\"https://tile.openstreetmap.org/\">OpenStreetMap tile servers</a>, under the <a target=\"_top\" rel=\"noopener\" href=\"https://operations.osmfoundation.org/policies/tiles/\">tile usage policy</a>. Data by <a target=\"_top\" rel=\"noopener\" href=\"http://openstreetmap.org\">OpenStreetMap</a>"
}
},
"sprite": "",
"glyphs": "https://api.maptiler.com/fonts/{fontstack}/{range}.pbf",
"layers": [
{
"id": "osm",
"type": "raster",
"source": "osm"
}
]
}
+34 -37
View File
@@ -1,10 +1,10 @@
from copy import deepcopy
from statistics import mean
from typing import List
import dash_bootstrap_components as dbc
import dash_table
import numpy as np
from dash_core_components import Graph
from dash_table.Format import Format, Scheme, Symbol
from dateutil.relativedelta import relativedelta
@@ -55,37 +55,34 @@ consumption_fig = ERROR_DIV
consumption_df = ERROR_DIV
trips_map = ERROR_DIV
consumption_fig_by_speed = ERROR_DIV
consumption_graph_by_temp = ERROR_DIV
consumption_fig_by_temp = ERROR_DIV
table_fig = ERROR_DIV
pandas_options.display.float_format = '${:.2f}'.format
info = ""
battery_info = ERROR_DIV
battery_table = None
consumption_df_dict = None
SUMMARY_CARDS = {"Average consumption": {"text": None, "src": "static/images/consumption.svg"},
"Average emission": {"text": None, "src": "static/images/pollution.svg"},
"Average charge speed": {"text": None, "src": "static/images/battery-charge-line.svg"},
"Electricity consumption": {"text": None, "src": "static/images/electricity bill.svg"}
SUMMARY_CARDS = {"Average consumption": {"text": None, "src": "assets/images/consumption.svg"},
"Average emission": {"text": None, "src": "assets/images/pollution.svg"},
"Average charge speed": {"text": None, "src": "assets/images/battery-charge-line.svg"},
"Electricity consumption": {"text": None, "src": "assets/images/electricity bill.svg"}
}
# pylint: disable=too-many-locals
def get_figures(trips: Trips, charging: List[dict]):
global consumption_fig, consumption_df, trips_map, consumption_fig_by_speed, table_fig, info, battery_info, \
battery_table, consumption_graph_by_temp
lats = []
lons = []
names = []
for trip in trips:
for points in trip.positions:
lats = np.append(lats, points.latitude)
lons = np.append(lons, points.longitude)
names = np.append(names, [str(trip.start_at)])
lats = np.append(lats, None)
lons = np.append(lons, None)
names = np.append(names, None)
trips_map = px.line_mapbox(lat=lats, lon=lons, hover_name=names,
mapbox_style="stamen-terrain", zoom=12)
battery_table, consumption_fig_by_temp, consumption_df_dict
lats = [42, 41]
lons = [1, 2]
names = ["undefined", "undefined"]
trips_map = px.line_mapbox(lat=lats, lon=lons, hover_name=names, zoom=12, mapbox_style="/assets/style2.json")
trips_map.add_trace(go.Scattermapbox(
mode="markers",
marker={"symbol": "marker", "size": 20},
lon=[lons[0]], lat=[lats[0]],
showlegend=False, name="Last Position"))
# table
nb_format = Format(precision=2, scheme=Scheme.fixed, symbol=Symbol.yes) # pylint: disable=no-member
table_fig = dash_table.DataTable(
@@ -120,20 +117,19 @@ def get_figures(trips: Trips, charging: List[dict]):
page_size=50
)
# consumption_fig
consumption_df = DataFrame.from_records(trips.get_long_trips())
consumption_fig = px.histogram(consumption_df, x="date", y="consumption_km", title='Consumption of the car',
consumption_df_dict = trips.get_long_trips()
consumption_fig = px.histogram(x=[0], y=[1], title='Consumption of the car',
histfunc="avg")
consumption_fig.update_layout(yaxis_title="Consumption kWh/100Km")
consumption_fig_by_speed = px.histogram(consumption_df, x="speed", y="consumption_km", histfunc="avg",
consumption_fig_by_speed = px.histogram(x=[0], y=[1], histfunc="avg",
title="Consumption by speed")
consumption_fig_by_speed.update_traces(xbins_size=15)
consumption_fig_by_speed.update_layout(bargap=0.05)
consumption_fig_by_speed.add_trace(
go.Scatter(mode="markers", x=consumption_df["speed"], y=consumption_df["consumption_km"],
name="Trips"))
consumption_fig_by_speed.add_trace(go.Scatter(mode="markers", x=[0],
y=[0], name="Trips"))
consumption_fig_by_speed.update_layout(xaxis_title="average Speed km/h", yaxis_title="Consumption kWh/100Km")
kw_per_km = float(consumption_df["consumption_km"].mean())
kw_per_km = mean([t.consumption_km for t in trips])
info = "Average consumption: {:.1f} kWh/100km".format(kw_per_km)
# charging
@@ -154,7 +150,7 @@ def get_figures(trips: Trips, charging: List[dict]):
SUMMARY_CARDS["Average emission"]["text"] = [html.P(f"{co2_per_km:.1f} g/km"), html.P(f"{co2_per_kw:.1f} g/kWh")]
SUMMARY_CARDS["Electricity consumption"]["text"] = [f"{total_elec:.0f} kWh", html.Br(), \
f"{total_elec * price_kw:.0f} {ElecPrice.currency}"]
SUMMARY_CARDS["Average consumption"]["text"] = f"{consumption_df['consumption_km'].mean():.1f} kWh/100km"
SUMMARY_CARDS["Average consumption"]["text"] = f"{kw_per_km:.1f} kWh/100km"
battery_table = dash_table.DataTable(
id='battery-table',
sort_action='native',
@@ -183,21 +179,22 @@ def get_figures(trips: Trips, charging: List[dict]):
}
],
)
consumption_by_temp_df = consumption_df[consumption_df["consumption_by_temp"].notnull()]
if len(consumption_by_temp_df) > 0:
consumption_fig_by_temp = px.histogram(consumption_by_temp_df, x="consumption_by_temp", y="consumption_km",
consumption_fig_by_temp = None
temp_value = False
for trip in trips:
if trip.get_temperature() is not None:
temp_value = True
break
if temp_value:
consumption_fig_by_temp = px.histogram(x=[0], y=[0],
histfunc="avg", title="Consumption by temperature")
consumption_fig_by_temp.update_traces(xbins_size=2)
consumption_fig_by_temp.update_layout(bargap=0.05)
consumption_fig_by_temp.add_trace(
go.Scatter(mode="markers", x=consumption_by_temp_df["consumption_by_temp"],
y=consumption_by_temp_df["consumption_km"], name="Trips"))
go.Scatter(mode="markers", x=[0],
y=[0], name="Trips"))
consumption_fig_by_temp.update_layout(xaxis_title="average temperature in °C",
yaxis_title="Consumption kWh/100Km")
consumption_graph_by_temp = html.Div(Graph(figure=consumption_fig_by_temp), id="consumption_graph_by_temp")
else:
consumption_graph_by_temp = html.Div(Graph(style={'display': 'none'}), id="consumption_graph_by_temp")
return True
+46 -13
View File
@@ -55,14 +55,28 @@ def diff_dashtable(data, data_previous, row_id_name="row_id"):
return changes
figures_list = ["consumption_fig", "consumption_fig_by_speed", "consumption_graph_by_temp", "trips_map"]
y_list = [["consumption_km"], ["consumption_km", "consumption_km"], ["consumption_km", "consumption_km"],
["long", "start_at"]]
x_list = ["start_at", "speed", "consumption_by_temp", "lat"]
outputs = [Output(id, "figure") for id in figures_list]
dash_app.clientside_callback(
"""
function(data,range, figures) {
return filter_dataset(data,range,figures,%s, %s);
}
""" % (x_list, y_list),
*outputs,
Input('clientside-data-store', 'data'),
Input('date-slider', 'value'),
Input('clientside-figure-store', 'data'))
def create_callback(): # noqa: MC0001
global CALLBACK_CREATED
if not CALLBACK_CREATED:
@dash_app.callback(Output('trips_map', 'figure'),
Output('consumption_fig', 'figure'),
Output('consumption_fig_by_speed', 'figure'),
Output('consumption_graph_by_temp', 'children'),
Output('summary-cards', 'children'),
@dash_app.callback(Output('summary-cards', 'children'),
Output('tab_trips_fig', 'children'),
Output('tab_charge', 'children'),
Output('date-slider', 'max'),
@@ -78,8 +92,7 @@ def create_callback(): # noqa: MC0001
filtered_trips.append(trip)
filtered_chargings = Charging.get_chargings(mini, maxi)
figures.get_figures(filtered_trips, filtered_chargings)
return figures.trips_map, figures.consumption_fig, figures.consumption_fig_by_speed, \
figures.consumption_graph_by_temp, create_card(figures.SUMMARY_CARDS), \
return create_card(figures.SUMMARY_CARDS), \
figures.table_fig, figures.battery_table, max_millis, step, marks
@dash_app.callback(Output(EMPTY_DIV, "children"),
@@ -159,6 +172,21 @@ def get_vehicle_info(vin):
return response
STYLE_CACHE = None
@app.route("/assets/style2.json")
def get_style():
global STYLE_CACHE
if not STYLE_CACHE:
with open(app.root_path + "/assets/style.json", "r") as f:
res = json.loads(f.read())
STYLE_CACHE = res
url_root = request.url_root
STYLE_CACHE["sprite"] = url_root + "assets/sprites/osm-liberty@2x"
return jsonify(STYLE_CACHE)
@app.route('/charge_now/<string:vin>/<int:charge>')
def charge_now(vin, charge):
return jsonify(myp.charge_now(vin, charge != 0))
@@ -312,9 +340,9 @@ def create_card(card: dict):
dbc.Card([
html.H4(tile, className="card-title text-center"),
dbc.Row([
dbc.Col(dbc.CardBody(text, style={"white-space": "nowrap", "font-size": "160%"}),
dbc.Col(dbc.CardBody(text, style={"whiteS pace": "nowrap", "fontSize": "160%"}),
className="text-center"),
dbc.Col(dbc.CardImg(src=value.get("src", Component.UNDEFINED), style={"max-height": "7rem"}))
dbc.Col(dbc.CardImg(src=value.get("src", Component.UNDEFINED), style={"maxHeight": "7rem"}))
],
className="align-items-center flex-nowrap")
], className="h-100 p-2"),
@@ -331,10 +359,12 @@ def serve_layout():
figures.get_figures(trips, chargings)
summary_tab = [dbc.Container(dbc.Row(id="summary-cards",
children=create_card(figures.SUMMARY_CARDS)), fluid=True),
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'})
dcc.Graph(id="consumption_fig"),
dcc.Graph(id="consumption_fig_by_speed"),
dcc.Graph(id="consumption_graph_by_temp",
style={'display': 'none'} if figures.consumption_fig_by_temp is None else {},
)]
maps = dcc.Graph(id="trips_map", style={"height": '90vh'})
create_callback()
range_slider = dcc.RangeSlider(
id='date-slider',
@@ -350,6 +380,9 @@ def serve_layout():
logger.warning("Failed to generate figure, there is probably not enough data yet", exc_info_debug=True)
range_slider = html.Div()
data_div = html.Div([
dcc.Store(id='clientside-figure-store', data=[figures.consumption_fig, figures.consumption_fig_by_speed,
figures.consumption_fig_by_temp, figures.trips_map]),
dcc.Store(id='clientside-data-store', data=figures.consumption_df_dict),
range_slider,
html.Div([
dbc.Tabs([