filter every element on clientside

This commit is contained in:
Florian Bezannier
2021-05-10 20:53:53 +02:00
parent 335c8f79bf
commit c111d87640
11 changed files with 3916 additions and 162 deletions
+6
View File
@@ -38,6 +38,12 @@ class Car:
def is_hybrid(self) -> bool:
return self.fuel_capacity > 0 and self.battery_power > 0
def has_battery(self):
return self.battery_power > 0
def has_fuel(self):
return self.fuel_capacity > 0
def get_status(self):
if self.status is not None:
return self.status
+2 -10
View File
@@ -13,17 +13,9 @@ class Charging:
elec_price: ElecPrice = ElecPrice(None)
@staticmethod
def get_chargings(mini=None, maxi=None) -> List[dict]:
def get_chargings() -> List[dict]:
conn = Database.get_db()
if mini is not None:
if maxi is not None:
res = conn.execute("select * from battery WHERE start_at>=? and start_at<=?", (mini, maxi)).fetchall()
else:
res = conn.execute("select * from battery WHERE start_at>=?", (mini,)).fetchall()
elif maxi is not None:
res = conn.execute("select * from battery WHERE start_at<=?", (maxi,)).fetchall()
else:
res = conn.execute("select * from battery").fetchall()
res = conn.execute("select * from battery ORDER BY start_at").fetchall()
conn.close()
return list(map(dict, res))
+21 -24
View File
@@ -2,7 +2,6 @@ import logging
from statistics import mean
from typing import List, Dict
from dateutil import tz
from geojson import Feature, FeatureCollection, MultiLineString
from libs.car import Cars, Car
@@ -38,6 +37,7 @@ class Trip:
self.car: Car = None
self.altitude_diff = None
self.temperatures = []
self.id = None
def add_points(self, latitude, longitude):
self.positions.append(Points(latitude, longitude))
@@ -78,19 +78,22 @@ class Trip:
"average consumption": self.consumption_km,
"average consumption fuel": self.consumption_fuel_km})
def get_info(self, row_id=None):
res = {"start_at": self.start_at.astimezone(tz.tzlocal()).replace(tzinfo=None).strftime("%x %X"),
# convert to naive tz,
"duration": self.duration * 60, "speed_average": self.speed_average,
"consumption_km": self.consumption_km, "consumption_fuel_km": self.consumption_fuel_km,
"distance": self.distance, "mileage": self.mileage, "altitude_diff": self.altitude_diff}
if row_id is not None:
res["id"] = row_id
return res
def get_consumption(self):
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 get_info(self):
res = {"consumption_km": self.consumption_km, "start_at": self.start_at,
"consumption_by_temp": self.get_temperature(), "positions": self.get_positions(),
"duration": self.duration * 60, "speed_average": self.speed_average, "distance": self.distance,
"mileage": self.mileage, "altitude_diff": self.altitude_diff, "id": self.id,
"consumption": self.consumption
}
if self.car.has_battery():
res["consumption_km"] = self.consumption_km
if self.car.has_fuel():
res["consumption_fuel_km"] = self.consumption_fuel_km
return res
def set_altitude_diff(self, start, end):
try:
@@ -110,14 +113,14 @@ class Trip:
class Trips(list):
def __init__(self, *args):
list.__init__(self, *args)
self.trip_num = 1
def to_geo_json(self):
feature_collection = FeatureCollection(self)
return feature_collection
def get_long_trips(self):
res = [trip.get_consumption() for trip in self if trip.consumption > 1.8]
return res
def get_trips_as_dict(self):
return [trip.get_info() for trip in self]
def get_distance(self):
return self[-1].mileage - self[0].mileage
@@ -125,6 +128,8 @@ class Trips(list):
def check_and_append(self, trip: Trip):
if trip.consumption_km <= trip.car.max_elec_consumption and \
trip.consumption_fuel_km <= trip.car.max_fuel_consumption:
trip.id = self.trip_num
self.trip_num += 1
self.append(trip)
return True
logger.debugv("trip discarded")
@@ -218,11 +223,3 @@ class Trips(list):
trips_by_vin[vin] = trips
conn.close()
return trips_by_vin
def get_info(self):
res = []
row_id = 1
for trip in self:
res.append(trip.get_info(row_id))
row_id += 1
return res
+189 -11
View File
@@ -1,17 +1,91 @@
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"]));
class Avg{
constructor(){
this.total = 0;
this.count = 0;
}
add_value(value){
if (typeof value === 'number') {
this.count++;
this.total = ((this.total*(this.count-1))/this.count) + (value/this.count);
}
}
average(){
return this.total;
}
static get_average_key(array, key){
var avg = new Avg()
array.forEach(function(obj){ avg.add_value(obj[key])})
return avg.average();
}
}
var logger = function()
{
var oldConsoleLog = null;
var pub = {};
pub.enableLogger = function enableLogger()
{
if(oldConsoleLog == null)
return;
window['console']['log'] = oldConsoleLog;
};
pub.disableLogger = function disableLogger()
{
oldConsoleLog = console.log;
window['console']['log'] = function() {};
};
return pub;
}();
function add_date_str(data, date_key){
var date_option = [undefined, {"hour":"numeric", "minute":"numeric"}]
for([dataset_name, dataset] of Object.entries(data)){
dataset.forEach(function (row) {
date_key[dataset_name].forEach(function (key) {
var date= new Date(row[key]);
row[key] = date
row[key + "_str"] = date.toLocaleDateString(...date_option);
})
})
}
}
function filter_dataset(data,range){
function date_from_iso_str(st){
return new Date(st).getTime()/1000
}
function is_in_range(st){
ts_date = date_from_iso_str(st);
return ts_date >= range[0] && ts_date <= range[1]
}
var res = {"trips": data["trips"].filter(line => is_in_range(line["start_at"])),
"chargings": data["chargings"].filter(line => is_in_range(line["start_at"]))};
console.log("filtered_dataset", res);
return res;
}
function filter_short_trip(data){
var long_trips = {"trips": data["trips"].filter(line => line["distance"]>10),
"chargings": data["chargings"]};
console.log("long trips:" , long_trips)
return long_trips;
}
function update_figures(data, old_figure, x,y) {
var trips = data["trips"]
var figures = [];
var i=0
y.forEach(function (y_label){
var x_label=x[i]
var figure = Object.assign({}, old_figure[i]);
i++;
// console.log(old_figure[i]);
// var unique_y_label = y[i].filter((v, i, a) => a.indexOf(v) === i);
//var data_nonnull = data_filtered
//var data_nonnull = trips
// unique_y_label.forEach(function(label) {
// data_nonnull = data_nonnull.filter(line => line[label]);
// });
@@ -20,7 +94,7 @@ function filter_dataset(data, range, old_figure, x,y) {
figure["data"][0]["lon"] = []
figure["data"][0]["hovertext"] = []
var trip = null;
for (trip of data_filtered) {
for (trip of trips) {
x_pos = trip["positions"][x_label]
figure["data"][0]["lat"].push(...x_pos, null);
figure["data"][0]["lon"].push(...trip["positions"][y_label[0]]);
@@ -33,15 +107,119 @@ function filter_dataset(data, range, old_figure, x,y) {
figure.data[1].lon = [figure.layout.mapbox.center.lon]
}
else {
x_values = data_filtered.map(a => a[x_label])
x_values = trips.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]["y"] = trips.map(a => a[y_label[j]]);
figure["data"][j]["x"] = x_values
}
}
// console.log(figure);
console.log(x_label, figure);
figures.push(figure);
});
return figures;
}
function update_table(data, tables){
console.log("tables", tables);
figures = [];
tables.forEach(function (table){
figures.push(data[table.src]);
})
return figures;
}
function update_cards_value(data){
res = {}
avg_co2=new Avg();
avg_kw = new Avg();
avg_time = new Avg()
avg_price = new Avg();
data["chargings"].forEach(function(charge){
diff = ((new Date(charge["stop_at"])) - (new Date(charge["start_at"])))/3600000;
avg_kw.add_value(charge["kw"]);
avg_co2.add_value(charge["co2"]);
avg_price.add_value(charge["price"]);
if(diff > 0){
avg_time.add_value(diff);
}
})
total_distance = data["trips"][data["trips"].length-1]["mileage"]-data["trips"][0]["mileage"]
avg_kw = avg_kw.average();
avg_co2 = avg_co2.average()
avg_price_kw = avg_price.average()/avg_kw;
res["avg_consum_kw"] = Avg.get_average_key(data["trips"], "consumption_km")
res["avg_emission_kw"] = avg_co2;
res["avg_emission_km"] = res["avg_emission_kw"]*res["avg_consum_kw"]/100;
res["avg_chg_speed"] = avg_kw/avg_time.average()
res["elec_consum_kw"] = total_distance*res["avg_consum_kw"]/100;
res["elec_consum_price"] = avg_price_kw*res["elec_consum_kw"]
res["avg_consum_price"] = avg_price_kw*res["avg_consum_kw"]
//console.log(res);
for (const [key, value] of Object.entries(res)) {
document.getElementById(key).innerHTML=value.toPrecision(3);
}
}
function sort_dataset(ctx, data, tables){
var table_id = ctx.prop_id.split(".")[0];
if(ctx.value.length > 0){
var asc = ctx.value[0].direction==='asc';
var column_id = ctx.value[0].column_id;
var table = tables.filter(table => table.table_id === table_id)[0];
var sorted = null;
if (column_id.endsWith("_str")){
column_id = column_id.slice(0, -4);
sorted = data[table.src].sort(function(a,b){
return a[column_id] - b[column_id];
});
}
else if(typeof data[table.src][0][column_id] == 'number'){
sorted = data[table.src].sort(function(a,b){
return a[column_id] - b[column_id];
});
}
else {
sorted = data[table.src].sort((a, b) => a[column_id].localeCompare(b[column_id]));
}
if(asc===false){
sorted = sorted.reverse();
}
data[table.src]=sorted;
}
}
function filter_and_sort(data,range, figures, p, log) {
if(log>10){
logger.disableLogger();
}
console.log("figures:", figures);
console.log("data:", data)
var ctx = dash_clientside.callback_context.triggered;
console.log("ctx", ctx);
var out_figures = [];
if(ctx.length > 0 && ctx[0].prop_id.endsWith("sort_by")){
var data_filtered = filter_dataset(data, range);
sort_dataset(ctx[0], data_filtered, p.table_src);
out_figures.push(...update_table(data_filtered, p.table_src));
out_figures.push(...figures.graph);
out_figures.push(...figures.maps);
}
else{
add_date_str(data,p.date_columns);
var data_filtered = filter_dataset(data, range);
out_figures.push(...update_table(data_filtered, p.table_src));
console.log(data_filtered["trips"].length);
long_trips = filter_short_trip(data_filtered);
console.log("trips", data_filtered["trips"].length);
console.log("long_trips", long_trips["trips"].length);
out_figures.push(...update_figures(long_trips, figures["graph"], p.graph_x_label, p.graph_y_label));
out_figures.push(...update_figures(data_filtered, figures["maps"], p.map_x_label, p.map_y_label));
update_cards_value(long_trips);
}
return out_figures;
}
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

+121
View File
@@ -0,0 +1,121 @@
import json
from logging import DEBUG
from dash.dependencies import Output, Input
from dash_core_components import Store
from mylogger import logger
class Graph:
def __init__(self, graph_id, x, y: [], figure):
self.graph_id = graph_id
self.x = x
self.y = y
self.figure = figure
class Table:
def __init__(self, table_id, src, figure):
self.table_id = table_id
self.src = src
self.figure = figure
self.date_columns = []
def figures_to_dict(figures):
el_list = []
for figure in figures:
res = {}
for key, value in figure.__dict__.items():
if key != "figure":
res[key] = value
el_list.append(res)
return el_list
class Figure_Filter:
def __init__(self):
self.graphs = []
self.tables = []
self.maps = []
self.src = {}
def add_map(self, dash_Graph, latitude, longitude, figure):
self.maps.append(Graph(dash_Graph.id, latitude, longitude, figure))
return dash_Graph
def add_graph(self, dash_Graph, x, y, figure):
self.graphs.append(Graph(dash_Graph.id, x, y, figure))
return dash_Graph
def add_table(self, src, figure):
table = Table(figure.id, src, figure)
table.date_columns = [col["id"][:-4] for col in figure.columns if col["type"] == "datetime" and
col["id"].endswith("_str")]
self.tables.append(table)
def __get_table_date_column_id(self):
res = {table.src: table.date_columns for table in self.tables}
return res
def __get_table_src(self):
return [table.src for table in self.tables]
def __get_figures(self):
return {"graph": [graph.figure for graph in self.graphs],
"tables": [table.figure for table in self.tables],
"maps": [map.figure for map in self.maps]}
def __get_output(self) -> list:
outputs = [Output(table.table_id, "data") for table in self.tables]
outputs.extend([Output(graph.graph_id, "figure") for graph in self.graphs])
outputs.extend([Output(graph.graph_id, "figure") for graph in self.maps])
return outputs
def __get_graph_x_label(self, graphs):
return [graph.x for graph in graphs]
def __get_graph_y_label(self, graphs):
return [graph.y for graph in graphs]
def __get_table_input_sort_by(self):
inputs = [Input(table.table_id, 'sort_by') for table in self.tables]
return inputs
def gen_unused_variable(self):
res = ", ".join([chr(i) for i in range(ord('a'), ord('a') + len(self.tables))])
return res
def get_params(self):
params = json.dumps({
"date_columns": self.__get_table_date_column_id(),
"table_src": figures_to_dict(self.tables),
"graph_x_label": self.__get_graph_x_label(self.graphs),
"graph_y_label": self.__get_graph_y_label(self.graphs),
"map_x_label": self.__get_graph_x_label(self.maps),
"map_y_label": self.__get_graph_y_label(self.maps)
}, indent=4)
return params
def get_clientside_callback(self):
if logger.isEnabledFor(DEBUG):
log_level = 10
else:
log_level = 20
fct_def = f"""function(data,range, figures, {self.gen_unused_variable()}) {{
var params={self.get_params()};
var log_level={log_level};
return filter_and_sort(data,range, figures, params, log_level)
}}"""
res = [fct_def,
*self.__get_output(),
Input('clientside-data-store', 'data'),
Input('date-slider', 'value'),
Input('clientside-figure-store', 'data'),
*self.__get_table_input_sort_by()]
return res
def get_store(self):
return [Store(id='clientside-figure-store', data=self.__get_figures()),
Store(id='clientside-data-store', data=self.src)]
+54 -60
View File
@@ -1,7 +1,4 @@
from copy import deepcopy
from statistics import mean
from typing import List
import dash_bootstrap_components as dbc
import dash_table
@@ -10,13 +7,11 @@ from dash_table.Format import Format, Scheme, Symbol
from dateutil.relativedelta import relativedelta
import plotly.express as px
import plotly.graph_objects as go
from pandas import DataFrame
from pandas import options as pandas_options
import dash_html_components as html
from libs.car import Car
from libs.elec_price import ElecPrice
from trip import Trips, Trip
from trip import Trip
from web.db import Database
@@ -57,21 +52,41 @@ trips_map = ERROR_DIV
consumption_fig_by_speed = 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": "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"}
def card_value_div(card_id, unit, value="-"):
return html.Div([html.Div(value, id=card_id, className="mr-2"), html.Div(unit)],
className="d-flex flex-row justify-content-center")
AVG_CHARGE_SPEED = "avg_chg_speed"
AVG_EMISSION_KM = "avg_emission_km"
AVG_EMISSION_KW = "avg_emission_kw"
ELEC_CONSUM_KW = "elec_consum_kw"
ELEC_CONSUM_PRICE = "elec_consum_price"
AVG_CONSUM_KW = "avg_consum_kw"
AVG_CONSUM_PRICE = "avg_consum_price"
SUMMARY_CARDS = {"Average consumption": {"text": [card_value_div(AVG_CONSUM_KW, "kWh/100km"),
card_value_div(AVG_CONSUM_PRICE, f"{ElecPrice.currency}/100km")],
"src": "assets/images/consumption.svg"},
"Average emission": {"text": [card_value_div(AVG_EMISSION_KM, " g/km"),
card_value_div(AVG_EMISSION_KW, "g/kWh")],
"src": "assets/images/pollution.svg"},
"Average charge speed": {"text": [card_value_div(AVG_CHARGE_SPEED, " kW")],
"src": "assets/images/battery-charge-line.svg"},
"Electricity consumption": {"text": [card_value_div(ELEC_CONSUM_KW, "kWh"),
card_value_div(ELEC_CONSUM_PRICE, ElecPrice.currency)],
"src": "assets/images/electricity bill.svg"}
}
# pylint: disable=too-many-locals
def get_figures(trips: Trips, charging: List[dict]):
def get_figures(car: Car):
global consumption_fig, consumption_df, trips_map, consumption_fig_by_speed, table_fig, info, battery_info, \
battery_table, consumption_fig_by_temp, consumption_df_dict
lats = [42, 41]
@@ -85,12 +100,17 @@ def get_figures(trips: Trips, charging: List[dict]):
showlegend=False, name="Last Position"))
# table
nb_format = Format(precision=2, scheme=Scheme.fixed, symbol=Symbol.yes) # pylint: disable=no-member
style_cell_conditional = []
if car.is_electric():
style_cell_conditional.append({'if': {'column_id': 'consumption_fuel_km', }, 'display': 'None', })
if car.is_thermal():
style_cell_conditional.append({'if': {'column_id': 'consumption_km', }, 'display': 'None', })
table_fig = dash_table.DataTable(
id='trips-table',
sort_action='native',
sort_action='custom',
sort_by=[{'column_id': 'id', 'direction': 'desc'}],
columns=[{'id': 'id', 'name': '#', 'type': 'numeric'},
{'id': 'start_at', 'name': 'start at', 'type': 'datetime'},
{'id': 'start_at_str', 'name': 'start at', 'type': 'datetime'},
{'id': 'duration', 'name': 'duration', 'type': 'numeric',
'format': deepcopy(nb_format).symbol_suffix(" min").precision(0)},
{'id': 'speed_average', 'name': 'average speed', 'type': 'numeric',
@@ -113,50 +133,31 @@ def get_figures(trips: Trips, charging: List[dict]):
"text-decoration": "underline"
}
],
data=trips.get_info(),
style_cell_conditional=style_cell_conditional,
data=[],
page_size=50
)
# consumption_fig
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", xaxis_title="date")
consumption_fig_by_speed = px.histogram(x=[0], y=[1], histfunc="avg",
consumption_fig_by_speed = px.histogram(data_frame=[{"start_at": 1, "speed_average": 2}], x="start_at",
y="speed_average", 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=[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 = mean([t.consumption_km for t in trips])
info = "Average consumption: {:.1f} kWh/100km".format(kw_per_km)
# charging
charging_data = DataFrame.from_records(charging)
co2_per_kw = __calculate_co2_per_kw(charging_data)
co2_per_km = co2_per_kw * kw_per_km / 100
try:
charge_speed = 3600 * charging_data["kw"].mean() / \
(charging_data["stop_at"] - charging_data["start_at"]).mean().total_seconds()
price_kw = (charging_data["price"] / charging_data["kw"]).mean()
total_elec = kw_per_km * trips.get_distance() / 100
except (TypeError, KeyError, ZeroDivisionError): # when there is no data yet:
charge_speed = 0
price_kw = 0
total_elec = 0
SUMMARY_CARDS["Average charge speed"]["text"] = f"{charge_speed:.2f} kW"
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"{kw_per_km:.1f} kWh/100km"
# battery_table
battery_table = dash_table.DataTable(
id='battery-table',
sort_action='native',
sort_by=[{'column_id': 'start_at', 'direction': 'desc'}],
columns=[{'id': 'start_at', 'name': 'start at', 'type': 'datetime'},
{'id': 'stop_at', 'name': 'stop at', 'type': 'datetime'},
sort_action='custom',
sort_by=[{'column_id': 'start_at_str', 'direction': 'desc'}],
columns=[{'id': 'start_at_str', 'name': 'start at', 'type': 'datetime'},
{'id': 'stop_at_str', 'name': 'stop at', 'type': 'datetime'},
{'id': 'start_level', 'name': 'start level', 'type': 'numeric'},
{'id': 'end_level', 'name': 'end level', 'type': 'numeric'},
{'id': 'co2', 'name': 'CO2', 'type': 'numeric',
@@ -166,7 +167,7 @@ def get_figures(trips: Trips, charging: List[dict]):
{'id': 'price', 'name': 'price', 'type': 'numeric',
'format': deepcopy(nb_format).symbol_suffix(" " + ElecPrice.currency).precision(2), 'editable': True}
],
data=charging,
data=[],
style_data_conditional=[
{
'if': {'column_id': ['start_level', "end_level"]},
@@ -175,26 +176,19 @@ def get_figures(trips: Trips, charging: List[dict]):
},
{
'if': {'column_id': 'price'},
'backgroundColor': 'rgb(230, 246, 254)'
'backgroundColor': '#ABE2FB'
}
],
)
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=[0],
y=[0], name="Trips"))
consumption_fig_by_temp.update_layout(xaxis_title="average temperature in °C",
yaxis_title="Consumption kWh/100Km")
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=[0],
y=[0], name="Trips"))
consumption_fig_by_temp.update_layout(xaxis_title="average temperature in °C",
yaxis_title="Consumption kWh/100Km")
return True
+33 -57
View File
@@ -1,5 +1,4 @@
import json
from datetime import datetime, timezone
from typing import List
import dash_bootstrap_components as dbc
@@ -12,6 +11,7 @@ import dash_daq as daq
import pandas as pd
from flask import jsonify, request, Response as FlaskResponse
from libs.car import Cars
from mylogger import logger
from trip import Trips
@@ -23,6 +23,8 @@ from web.app import app, dash_app, myp, chc
from web.db import Database
# pylint: disable=invalid-name
from web.figure_filter import Figure_Filter
RESPONSE = "-response"
EMPTY_DIV = "empty-div"
ABRP_SWITCH = 'abrp-switch'
@@ -55,46 +57,9 @@ 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('summary-cards', 'children'),
Output('tab_trips_fig', '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): # pylint: disable=unused-variable
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)
return create_card(figures.SUMMARY_CARDS), \
figures.table_fig, figures.battery_table, max_millis, step, marks
@dash_app.callback(Output(EMPTY_DIV, "children"),
[Input("battery-table", "data_timestamp")],
[State("battery-table", "data"),
@@ -106,13 +71,13 @@ def create_callback(): # noqa: MC0001
for changed_line in diff_data:
if changed_line['column_name'] == 'price':
conn = Database.get_db()
if not Database.set_chargings_price( conn, changed_line['start_at'],
if not Database.set_chargings_price(conn, changed_line['start_at'],
changed_line['current_value']):
logger.error("Can't find line to update in the database")
conn.close()
return ""
@dash_app.callback([Output("tab_battery_popup_graph", "children"), Output("tab_battery_popup", "is_open"), ],
@dash_app.callback([Output("tab_battery_popup_graph", "children"), Output("tab_battery_popup", "is_open")],
[Input("battery-table", "active_cell"),
Input("tab_battery_popup-close", "n_clicks")],
[State('battery-table', 'data'),
@@ -129,7 +94,7 @@ def create_callback(): # noqa: MC0001
[Input("trips-table", "active_cell"),
Input("tab_trips_popup-close", "n_clicks")],
State("tab_trips_popup", "is_open"))
def get_altitude(active_cell, close, is_open): # pylint: disable=unused-argument, unused-variable
def get_altitude_graph(active_cell, close, is_open): # pylint: disable=unused-argument, unused-variable
if is_open is None:
is_open = False
if active_cell is not None and active_cell["column_id"] in ["altitude_diff"] and not is_open:
@@ -275,12 +240,14 @@ def update_trips():
conn.close()
min_date = None
max_date = None
car = myp.vehicles_list[0] # todo handle multiple car
try:
trips_by_vin = Trips.get_trips(myp.vehicles_list)
trips = next(iter(trips_by_vin.values())) # todo handle multiple car
trips_by_vin = Trips.get_trips(Cars([car]))
trips = trips_by_vin[car.vin]
assert len(trips) > 0
min_date = trips[0].start_at
max_date = trips[-1].start_at
figures.get_figures(trips[0].car)
except (StopIteration, AssertionError):
logger.debug("No trips yet")
try:
@@ -304,6 +271,7 @@ def update_trips():
step = (max_millis - min_millis) / 100
marks = figures.get_marks_from_start_end(min_date, max_date)
cached_layout = None # force regenerate layout
figures.get_figures(car)
except (ValueError, IndexError):
logger.error("update_trips (slider): %s", exc_info=True)
except AttributeError:
@@ -333,14 +301,17 @@ def __get_control_tabs():
def create_card(card: dict):
res = []
for tile, value in card.items():
text = value["text"]
rows = value["text"]
# if isinstance(text, str):
# text = html.H3(text)
html_text = []
for row in rows:
html_text.append(html.Div(row, className="d-flex flex-row justify-content-center"))
res.append(html.Div(
dbc.Card([
html.H4(tile, className="card-title text-center"),
dbc.Row([
dbc.Col(dbc.CardBody(text, style={"whiteS pace": "nowrap", "fontSize": "160%"}),
dbc.Col(dbc.CardBody(html_text, style={"whiteSpace": "nowrap", "fontSize": "160%"}),
className="text-center"),
dbc.Col(dbc.CardImg(src=value.get("src", Component.UNDEFINED), style={"maxHeight": "7rem"}))
],
@@ -355,16 +326,23 @@ def serve_layout():
global cached_layout
if cached_layout is None:
logger.debug("Create new layout")
fig_filter = Figure_Filter()
try:
figures.get_figures(trips, chargings)
summary_tab = [dbc.Container(dbc.Row(id="summary-cards",
children=create_card(figures.SUMMARY_CARDS)), fluid=True),
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'})
summary_tab = [
dbc.Container(dbc.Row(id="summary-cards",
children=create_card(figures.SUMMARY_CARDS)), fluid=True),
fig_filter.add_graph(dcc.Graph(id="consumption_fig"), "start_at", ["consumption_km"],
figures.consumption_fig),
fig_filter.add_graph(dcc.Graph(id="consumption_fig_by_speed"), "speed_average",
["consumption_km"] * 2, figures.consumption_fig_by_speed),
fig_filter.add_graph(dcc.Graph(id="consumption_graph_by_temp"), "consumption_by_temp",
["consumption_km"] * 2, figures.consumption_fig_by_temp)]
maps = fig_filter.add_map(dcc.Graph(id="trips_map", style={"height": '90vh'}), "lat",
["long", "start_at"], figures.trips_map)
fig_filter.add_table("trips", figures.table_fig)
fig_filter.add_table("chargings", figures.battery_table)
fig_filter.src = {"trips": trips.get_trips_as_dict(), "chargings": chargings}
dash_app.clientside_callback(*fig_filter.get_clientside_callback())
create_callback()
range_slider = dcc.RangeSlider(
id='date-slider',
@@ -380,9 +358,7 @@ 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),
*fig_filter.get_store(),
range_slider,
html.Div([
dbc.Tabs([