geo-activity-playground 1.0.0__py3-none-any.whl → 1.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- geo_activity_playground/alembic/versions/dc8073871da7_add_plotspec_group_by.py +28 -0
- geo_activity_playground/core/config.py +1 -0
- geo_activity_playground/core/datamodel.py +41 -3
- geo_activity_playground/core/parametric_plot.py +101 -47
- geo_activity_playground/webui/app.py +7 -0
- geo_activity_playground/webui/blueprints/activity_blueprint.py +11 -10
- geo_activity_playground/webui/blueprints/auth_blueprint.py +3 -2
- geo_activity_playground/webui/blueprints/bubble_chart_blueprint.py +2 -1
- geo_activity_playground/webui/blueprints/calendar_blueprint.py +3 -2
- geo_activity_playground/webui/blueprints/eddington_blueprints.py +3 -2
- geo_activity_playground/webui/blueprints/entry_views.py +11 -11
- geo_activity_playground/webui/blueprints/equipment_blueprint.py +2 -1
- geo_activity_playground/webui/blueprints/explorer_blueprint.py +47 -13
- geo_activity_playground/webui/blueprints/export_blueprint.py +3 -2
- geo_activity_playground/webui/blueprints/hall_of_fame_blueprint.py +79 -0
- geo_activity_playground/webui/blueprints/photo_blueprint.py +65 -56
- geo_activity_playground/webui/blueprints/plot_builder_blueprint.py +38 -19
- geo_activity_playground/webui/blueprints/settings_blueprint.py +17 -0
- geo_activity_playground/webui/blueprints/summary_blueprint.py +114 -240
- geo_activity_playground/webui/columns.py +40 -7
- geo_activity_playground/webui/static/{browserconfig.xml → favicons/browserconfig.xml} +1 -1
- geo_activity_playground/webui/static/{site.webmanifest → favicons/site.webmanifest} +2 -2
- geo_activity_playground/webui/static/server-side-explorer.js +7 -2
- geo_activity_playground/webui/templates/activity/name.html.j2 +4 -4
- geo_activity_playground/webui/templates/activity/show.html.j2 +8 -8
- geo_activity_playground/webui/templates/eddington/distance.html.j2 +3 -3
- geo_activity_playground/webui/templates/eddington/elevation_gain.html.j2 +3 -3
- geo_activity_playground/webui/templates/elevation_eddington/index.html.j2 +3 -3
- geo_activity_playground/webui/templates/equipment/index.html.j2 +4 -4
- geo_activity_playground/webui/templates/explorer/server-side.html.j2 +5 -4
- geo_activity_playground/webui/templates/hall_of_fame/index.html.j2 +57 -0
- geo_activity_playground/webui/templates/home.html.j2 +2 -12
- geo_activity_playground/webui/templates/page.html.j2 +23 -37
- geo_activity_playground/webui/templates/photo/new.html.j2 +1 -1
- geo_activity_playground/webui/templates/plot-macros.html.j2 +72 -0
- geo_activity_playground/webui/templates/plot_builder/edit.html.j2 +12 -7
- geo_activity_playground/webui/templates/plot_builder/import-spec.html.j2 +24 -0
- geo_activity_playground/webui/templates/plot_builder/index.html.j2 +5 -0
- geo_activity_playground/webui/templates/settings/index.html.j2 +9 -0
- geo_activity_playground/webui/templates/settings/tile-source.html.j2 +33 -0
- geo_activity_playground/webui/templates/summary/index.html.j2 +23 -230
- geo_activity_playground/webui/templates/summary/vega-chart.html.j2 +3 -0
- {geo_activity_playground-1.0.0.dist-info → geo_activity_playground-1.2.0.dist-info}/METADATA +1 -1
- {geo_activity_playground-1.0.0.dist-info → geo_activity_playground-1.2.0.dist-info}/RECORD +73 -66
- /geo_activity_playground/webui/static/{bootstrap-dark-mode.js → bootstrap/bootstrap-dark-mode.js} +0 -0
- /geo_activity_playground/webui/static/{bootstrap.bundle.min.js → bootstrap/bootstrap.bundle.min.js} +0 -0
- /geo_activity_playground/webui/static/{bootstrap.min.css → bootstrap/bootstrap.min.css} +0 -0
- /geo_activity_playground/webui/static/{android-chrome-192x192.png → favicons/android-chrome-192x192.png} +0 -0
- /geo_activity_playground/webui/static/{android-chrome-512x512.png → favicons/android-chrome-512x512.png} +0 -0
- /geo_activity_playground/webui/static/{apple-touch-icon.png → favicons/apple-touch-icon.png} +0 -0
- /geo_activity_playground/webui/static/{favicon-16x16.png → favicons/favicon-16x16.png} +0 -0
- /geo_activity_playground/webui/static/{favicon-32x32.png → favicons/favicon-32x32.png} +0 -0
- /geo_activity_playground/webui/static/{favicon-48x48.png → favicons/favicon-48x48.png} +0 -0
- /geo_activity_playground/webui/static/{favicon.ico → favicons/favicon.ico} +0 -0
- /geo_activity_playground/webui/static/{favicon.svg → favicons/favicon.svg} +0 -0
- /geo_activity_playground/webui/static/{mstile-150x150.png → favicons/mstile-150x150.png} +0 -0
- /geo_activity_playground/webui/static/{web-app-manifest-192x192.png → favicons/web-app-manifest-192x192.png} +0 -0
- /geo_activity_playground/webui/static/{web-app-manifest-512x512.png → favicons/web-app-manifest-512x512.png} +0 -0
- /geo_activity_playground/webui/static/{Leaflet.fullscreen.min.js → leaflet/Leaflet.fullscreen.min.js} +0 -0
- /geo_activity_playground/webui/static/{MarkerCluster.Default.css → leaflet/MarkerCluster.Default.css} +0 -0
- /geo_activity_playground/webui/static/{MarkerCluster.css → leaflet/MarkerCluster.css} +0 -0
- /geo_activity_playground/webui/static/{fullscreen.png → leaflet/fullscreen.png} +0 -0
- /geo_activity_playground/webui/static/{fullscreen@2x.png → leaflet/fullscreen@2x.png} +0 -0
- /geo_activity_playground/webui/static/{leaflet.css → leaflet/leaflet.css} +0 -0
- /geo_activity_playground/webui/static/{leaflet.fullscreen.css → leaflet/leaflet.fullscreen.css} +0 -0
- /geo_activity_playground/webui/static/{leaflet.js → leaflet/leaflet.js} +0 -0
- /geo_activity_playground/webui/static/{leaflet.markercluster.js → leaflet/leaflet.markercluster.js} +0 -0
- /geo_activity_playground/webui/static/{vega-embed@6 → vega/vega-embed@6.js} +0 -0
- /geo_activity_playground/webui/static/{vega-lite@4 → vega/vega-lite@4.js} +0 -0
- /geo_activity_playground/webui/static/{vega@5 → vega/vega@5.js} +0 -0
- {geo_activity_playground-1.0.0.dist-info → geo_activity_playground-1.2.0.dist-info}/LICENSE +0 -0
- {geo_activity_playground-1.0.0.dist-info → geo_activity_playground-1.2.0.dist-info}/WHEEL +0 -0
- {geo_activity_playground-1.0.0.dist-info → geo_activity_playground-1.2.0.dist-info}/entry_points.txt +0 -0
@@ -1,4 +1,3 @@
|
|
1
|
-
import collections
|
2
1
|
import datetime
|
3
2
|
|
4
3
|
import altair as alt
|
@@ -9,224 +8,41 @@ from flask import render_template
|
|
9
8
|
from flask import request
|
10
9
|
|
11
10
|
from ...core.activities import ActivityRepository
|
12
|
-
from ...core.activities import make_geojson_from_time_series
|
13
11
|
from ...core.config import Config
|
14
12
|
from ...core.datamodel import DB
|
15
13
|
from ...core.datamodel import PlotSpec
|
16
14
|
from ...core.meta_search import apply_search_query
|
17
15
|
from ...core.parametric_plot import make_parametric_plot
|
18
|
-
from ..columns import column_distance
|
19
|
-
from ..columns import column_elevation_gain
|
20
16
|
from ..columns import ColumnDescription
|
17
|
+
from ..columns import META_COLUMNS
|
21
18
|
from ..plot_util import make_kind_scale
|
22
19
|
from ..search_util import search_query_from_form
|
23
20
|
from ..search_util import SearchQueryHistory
|
24
21
|
|
25
22
|
|
26
|
-
def
|
27
|
-
repository: ActivityRepository,
|
28
|
-
config: Config,
|
29
|
-
search_query_history: SearchQueryHistory,
|
30
|
-
) -> Blueprint:
|
31
|
-
blueprint = Blueprint("summary", __name__, template_folder="templates")
|
32
|
-
|
33
|
-
@blueprint.route("/")
|
34
|
-
def index():
|
35
|
-
query = search_query_from_form(request.args)
|
36
|
-
search_query_history.register_query(query)
|
37
|
-
activities = apply_search_query(repository.meta, query)
|
38
|
-
|
39
|
-
kind_scale = make_kind_scale(repository.meta, config)
|
40
|
-
df = activities
|
41
|
-
|
42
|
-
nominations = nominate_activities(df)
|
43
|
-
|
44
|
-
return render_template(
|
45
|
-
"summary/index.html.j2",
|
46
|
-
plot_distance_heatmaps=plot_heatmaps(df, column_distance, config),
|
47
|
-
plot_elevation_gain_heatmaps=plot_heatmaps(
|
48
|
-
df, column_elevation_gain, config
|
49
|
-
),
|
50
|
-
plot_monthly_distance=plot_monthly_sums(df, column_distance, kind_scale),
|
51
|
-
plot_monthly_elevation_gain=plot_monthly_sums(
|
52
|
-
df, column_elevation_gain, kind_scale
|
53
|
-
),
|
54
|
-
plot_yearly_distance=plot_yearly_sums(df, column_distance, kind_scale),
|
55
|
-
plot_yearly_elevation_gain=plot_yearly_sums(
|
56
|
-
df, column_elevation_gain, kind_scale
|
57
|
-
),
|
58
|
-
plot_year_cumulative=plot_year_cumulative(df, column_distance),
|
59
|
-
plot_year_elevation_gain_cumulative=plot_year_cumulative(
|
60
|
-
df, column_elevation_gain
|
61
|
-
),
|
62
|
-
tabulate_year_kind_mean=tabulate_year_kind_mean(df, column_distance)
|
63
|
-
.reset_index()
|
64
|
-
.to_dict(orient="split"),
|
65
|
-
tabulate_year_kind_mean_elevation_gain=tabulate_year_kind_mean(
|
66
|
-
df, column_elevation_gain
|
67
|
-
)
|
68
|
-
.reset_index()
|
69
|
-
.to_dict(orient="split"),
|
70
|
-
plot_weekly_distance=plot_weekly_sums(df, column_distance, kind_scale),
|
71
|
-
plot_weekly_elevation_gain=plot_weekly_sums(
|
72
|
-
df, column_elevation_gain, kind_scale
|
73
|
-
),
|
74
|
-
nominations=[
|
75
|
-
(
|
76
|
-
repository.get_activity_by_id(activity_id),
|
77
|
-
reasons,
|
78
|
-
make_geojson_from_time_series(
|
79
|
-
repository.get_time_series(activity_id)
|
80
|
-
),
|
81
|
-
)
|
82
|
-
for activity_id, reasons in nominations.items()
|
83
|
-
],
|
84
|
-
query=query.to_jinja(),
|
85
|
-
custom_plots=[
|
86
|
-
(spec, make_parametric_plot(repository.meta, spec))
|
87
|
-
for spec in DB.session.scalars(sqlalchemy.select(PlotSpec)).all()
|
88
|
-
],
|
89
|
-
)
|
90
|
-
|
91
|
-
return blueprint
|
92
|
-
|
93
|
-
|
94
|
-
def nominate_activities(meta: pd.DataFrame) -> dict[int, list[str]]:
|
95
|
-
nominations: dict[int, list[str]] = collections.defaultdict(list)
|
96
|
-
|
97
|
-
_nominate_activities_inner(meta, "", nominations)
|
98
|
-
|
99
|
-
for kind, group in meta.groupby("kind"):
|
100
|
-
_nominate_activities_inner(group, f" for {kind}", nominations)
|
101
|
-
for equipment, group in meta.groupby("equipment"):
|
102
|
-
_nominate_activities_inner(group, f" with {equipment}", nominations)
|
103
|
-
|
104
|
-
return nominations
|
105
|
-
|
106
|
-
|
107
|
-
def _nominate_activities_inner(
|
108
|
-
meta: pd.DataFrame, title_suffix: str, nominations: dict[int, list[str]]
|
109
|
-
) -> None:
|
110
|
-
ratings = [
|
111
|
-
("distance_km", "Greatest distance", "{:.1f} km"),
|
112
|
-
("elapsed_time", "Longest elapsed time", "{}"),
|
113
|
-
("average_speed_moving_kmh", "Highest average moving speed", "{:.1f} km/h"),
|
114
|
-
("average_speed_elapsed_kmh", "Highest average elapsed speed", "{:.1f} km/h"),
|
115
|
-
("calories", "Most calories burnt", "{:.0f}"),
|
116
|
-
("steps", "Most steps", "{:.0f}"),
|
117
|
-
("elevation_gain", "Largest elevation gain", "{:.0f} m"),
|
118
|
-
]
|
119
|
-
|
120
|
-
for variable, title, format_str in ratings:
|
121
|
-
if variable in meta.columns and not pd.isna(meta[variable]).all():
|
122
|
-
i = meta[variable].idxmax()
|
123
|
-
value = meta.loc[i, variable]
|
124
|
-
format_applied = format_str.format(value)
|
125
|
-
nominations[i].append(f"{title}{title_suffix}: {format_applied}")
|
126
|
-
|
127
|
-
|
128
|
-
def plot_heatmaps(
|
129
|
-
meta: pd.DataFrame, column: ColumnDescription, config: Config
|
130
|
-
) -> dict[int, str]:
|
131
|
-
return {
|
132
|
-
year: alt.Chart(
|
133
|
-
meta.loc[(meta["year"] == year)],
|
134
|
-
title=f"Daily {column.display_name} Heatmap",
|
135
|
-
)
|
136
|
-
.mark_rect()
|
137
|
-
.encode(
|
138
|
-
alt.X("date(start):O", title="Day of month"),
|
139
|
-
alt.Y(
|
140
|
-
"yearmonth(start):O",
|
141
|
-
# scale=alt.Scale(reverse=True),
|
142
|
-
title="Year and month",
|
143
|
-
),
|
144
|
-
alt.Color(
|
145
|
-
f"sum({column.name})",
|
146
|
-
scale=alt.Scale(scheme=config.color_scheme_for_counts),
|
147
|
-
),
|
148
|
-
[
|
149
|
-
alt.Tooltip("yearmonthdate(start)", title="Date"),
|
150
|
-
alt.Tooltip(
|
151
|
-
f"sum({column.name})",
|
152
|
-
format=column.format,
|
153
|
-
title=f"Total {column.display_name} / {column.unit}",
|
154
|
-
),
|
155
|
-
alt.Tooltip(f"count({column.name})", title="Number of activities"),
|
156
|
-
],
|
157
|
-
)
|
158
|
-
.to_json(format="vega")
|
159
|
-
for year in sorted(meta["year"].unique())
|
160
|
-
}
|
161
|
-
|
162
|
-
|
163
|
-
def plot_monthly_sums(
|
164
|
-
meta: pd.DataFrame, column: ColumnDescription, kind_scale: alt.Scale
|
165
|
-
) -> str:
|
23
|
+
def plot_per_year_per_kind(df: pd.DataFrame, column: ColumnDescription) -> str:
|
166
24
|
return (
|
167
25
|
alt.Chart(
|
168
|
-
|
169
|
-
|
170
|
-
meta["start"]
|
171
|
-
>= pd.to_datetime(
|
172
|
-
datetime.datetime.now() - datetime.timedelta(days=2 * 365)
|
173
|
-
)
|
174
|
-
)
|
175
|
-
],
|
176
|
-
title=f"Monthly {column.display_name}",
|
26
|
+
df,
|
27
|
+
title=f"{column.display_name} per Year",
|
177
28
|
)
|
178
29
|
.mark_bar()
|
179
30
|
.encode(
|
180
|
-
alt.X("
|
31
|
+
alt.X("year:O", title="Year"),
|
181
32
|
alt.Y(
|
182
|
-
f"sum({column.name})",
|
183
|
-
title=f"{column.display_name} / {column.unit}",
|
33
|
+
f"sum({column.name})", title=f"{column.display_name} / {column.unit}"
|
184
34
|
),
|
185
|
-
alt.Color("kind",
|
186
|
-
alt.Column("year(start):O", title="Year"),
|
35
|
+
alt.Color("kind", title="Kind"),
|
187
36
|
[
|
188
|
-
alt.Tooltip("
|
37
|
+
alt.Tooltip("year", title="Year"),
|
189
38
|
alt.Tooltip("kind", title="Kind"),
|
190
39
|
alt.Tooltip(
|
191
40
|
f"sum({column.name})",
|
192
|
-
format=column.format,
|
193
|
-
title=f"Total {column.display_name} / {column.unit}",
|
194
|
-
),
|
195
|
-
alt.Tooltip(f"count({column.name})", title="Number of activities"),
|
196
|
-
],
|
197
|
-
)
|
198
|
-
.resolve_axis(x="independent")
|
199
|
-
.to_json(format="vega")
|
200
|
-
)
|
201
|
-
|
202
|
-
|
203
|
-
def plot_yearly_sums(
|
204
|
-
df: pd.DataFrame, column: ColumnDescription, kind_scale: alt.Scale
|
205
|
-
) -> str:
|
206
|
-
year_kind_total = (
|
207
|
-
df[["year", "kind", column.name, "hours"]]
|
208
|
-
.groupby(["year", "kind"])
|
209
|
-
.sum()
|
210
|
-
.reset_index()
|
211
|
-
)
|
212
|
-
|
213
|
-
return (
|
214
|
-
alt.Chart(year_kind_total, title=f"Total {column.display_name} per Year")
|
215
|
-
.mark_bar()
|
216
|
-
.encode(
|
217
|
-
alt.X("year:O", title="Year"),
|
218
|
-
alt.Y(column.name, title=f"{column.display_name} / {column.unit}"),
|
219
|
-
alt.Color("kind", scale=kind_scale, title="Kind"),
|
220
|
-
[
|
221
|
-
alt.Tooltip("year:O", title="Year"),
|
222
|
-
alt.Tooltip("kind", title="Kind"),
|
223
|
-
alt.Tooltip(
|
224
|
-
column.name,
|
225
41
|
title=f"{column.display_name} / {column.unit}",
|
226
|
-
format=column.format,
|
227
42
|
),
|
228
43
|
],
|
229
44
|
)
|
45
|
+
.interactive()
|
230
46
|
.to_json(format="vega")
|
231
47
|
)
|
232
48
|
|
@@ -250,7 +66,6 @@ def plot_year_cumulative(df: pd.DataFrame, column: ColumnDescription) -> str:
|
|
250
66
|
return (
|
251
67
|
alt.Chart(
|
252
68
|
year_cumulative,
|
253
|
-
width=500,
|
254
69
|
title=f"Cumulative {column.display_name} per Year",
|
255
70
|
)
|
256
71
|
.mark_line()
|
@@ -273,65 +88,124 @@ def plot_year_cumulative(df: pd.DataFrame, column: ColumnDescription) -> str:
|
|
273
88
|
)
|
274
89
|
|
275
90
|
|
276
|
-
def
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
.groupby(["year", "kind"])
|
282
|
-
.mean()
|
283
|
-
.reset_index()
|
284
|
-
)
|
285
|
-
|
286
|
-
year_kind_mean_distance = year_kind_mean.pivot(
|
287
|
-
index="year", columns="kind", values=column.name
|
288
|
-
)
|
289
|
-
|
290
|
-
return year_kind_mean_distance
|
291
|
-
|
292
|
-
|
293
|
-
def plot_weekly_sums(
|
294
|
-
df: pd.DataFrame, column: ColumnDescription, kind_scale: alt.Scale
|
295
|
-
) -> str:
|
296
|
-
week_kind_total_distance = (
|
297
|
-
df[["iso_year", "week", "kind", column.name]]
|
298
|
-
.groupby(["iso_year", "week", "kind"])
|
299
|
-
.sum()
|
300
|
-
.reset_index()
|
301
|
-
)
|
302
|
-
week_kind_total_distance["year_week"] = [
|
303
|
-
f"{year}-{week:02d}"
|
304
|
-
for year, week in zip(
|
305
|
-
week_kind_total_distance["iso_year"], week_kind_total_distance["week"]
|
91
|
+
def plot_per_iso_week(df: pd.DataFrame, column: ColumnDescription) -> str:
|
92
|
+
return (
|
93
|
+
alt.Chart(
|
94
|
+
df,
|
95
|
+
title=f"{column.display_name} per Week",
|
306
96
|
)
|
307
|
-
|
97
|
+
.mark_circle()
|
98
|
+
.encode(
|
99
|
+
alt.X("week:O", title="ISO Week"),
|
100
|
+
alt.Y("iso_year:O", title="ISO Year"),
|
101
|
+
alt.Size(
|
102
|
+
f"sum({column.name})", title=f"{column.display_name} / {column.unit}"
|
103
|
+
),
|
104
|
+
[
|
105
|
+
alt.Tooltip("iso_year", title="ISO Year"),
|
106
|
+
alt.Tooltip("week", title="ISO Week"),
|
107
|
+
alt.Tooltip(
|
108
|
+
f"sum({column.name})",
|
109
|
+
title=f"{column.display_name} / {column.unit}",
|
110
|
+
format=column.format,
|
111
|
+
),
|
112
|
+
],
|
113
|
+
)
|
114
|
+
.interactive()
|
115
|
+
.to_json(format="vega")
|
116
|
+
)
|
308
117
|
|
309
|
-
last_year = week_kind_total_distance["iso_year"].iloc[-1]
|
310
|
-
last_week = week_kind_total_distance["week"].iloc[-1]
|
311
118
|
|
119
|
+
def heatmap_per_day(df: pd.DataFrame, column: ColumnDescription) -> str:
|
312
120
|
return (
|
313
121
|
alt.Chart(
|
314
|
-
|
315
|
-
|
316
|
-
| (week_kind_total_distance["iso_year"] == last_year - 1)
|
317
|
-
& (week_kind_total_distance["week"] >= last_week)
|
318
|
-
],
|
319
|
-
title=f"Weekly {column.display_name}",
|
122
|
+
_filter_past_year(df),
|
123
|
+
title=f"{column.display_name} per day",
|
320
124
|
)
|
321
|
-
.
|
125
|
+
.mark_rect()
|
322
126
|
.encode(
|
323
|
-
alt.X("
|
324
|
-
alt.Y(
|
325
|
-
|
127
|
+
alt.X("iso_year_week:O", title="ISO Year and Week"),
|
128
|
+
alt.Y(
|
129
|
+
"iso_day:O",
|
130
|
+
# scale=alt.Scale(
|
131
|
+
# domain=list(range(1, 8)),
|
132
|
+
# range=[
|
133
|
+
# "Monday",
|
134
|
+
# "Tuesday",
|
135
|
+
# "Wednesday",
|
136
|
+
# "Thursday",
|
137
|
+
# "Friday",
|
138
|
+
# "Saturday",
|
139
|
+
# "Sunday",
|
140
|
+
# ],
|
141
|
+
# ),
|
142
|
+
title="ISO Weekday",
|
143
|
+
),
|
144
|
+
alt.Color(
|
145
|
+
f"sum({column.name})",
|
146
|
+
scale=alt.Scale(scheme="viridis"),
|
147
|
+
title=f"{column.display_name} / {column.unit}",
|
148
|
+
),
|
326
149
|
[
|
327
|
-
alt.Tooltip("
|
328
|
-
alt.Tooltip("
|
150
|
+
alt.Tooltip("iso_year_week", title="ISO Year and Week"),
|
151
|
+
alt.Tooltip("iso_day", title="ISO Day"),
|
329
152
|
alt.Tooltip(
|
330
|
-
column.name,
|
153
|
+
f"sum({column.name})",
|
331
154
|
title=f"{column.display_name} / {column.unit}",
|
332
155
|
format=column.format,
|
333
156
|
),
|
334
157
|
],
|
335
158
|
)
|
159
|
+
.interactive()
|
336
160
|
.to_json(format="vega")
|
337
161
|
)
|
162
|
+
|
163
|
+
|
164
|
+
def _filter_past_year(df: pd.DataFrame) -> pd.DataFrame:
|
165
|
+
now = datetime.datetime.combine(datetime.date.today(), datetime.time.min)
|
166
|
+
start = now - datetime.timedelta(days=365)
|
167
|
+
return df.loc[df["start"] >= start]
|
168
|
+
|
169
|
+
|
170
|
+
def make_summary_blueprint(
|
171
|
+
repository: ActivityRepository,
|
172
|
+
config: Config,
|
173
|
+
search_query_history: SearchQueryHistory,
|
174
|
+
) -> Blueprint:
|
175
|
+
blueprint = Blueprint("summary", __name__, template_folder="templates")
|
176
|
+
|
177
|
+
@blueprint.route("/")
|
178
|
+
def index():
|
179
|
+
query = search_query_from_form(request.args)
|
180
|
+
search_query_history.register_query(query)
|
181
|
+
activities = apply_search_query(repository.meta, query)
|
182
|
+
|
183
|
+
kind_scale = make_kind_scale(repository.meta, config)
|
184
|
+
df = activities
|
185
|
+
|
186
|
+
return render_template(
|
187
|
+
"summary/index.html.j2",
|
188
|
+
query=query.to_jinja(),
|
189
|
+
custom_plots=[
|
190
|
+
(spec, make_parametric_plot(repository.meta, spec))
|
191
|
+
for spec in DB.session.scalars(sqlalchemy.select(PlotSpec)).all()
|
192
|
+
],
|
193
|
+
plot_per_year_per_kind={
|
194
|
+
column.display_name: plot_per_year_per_kind(df, column)
|
195
|
+
for column in META_COLUMNS
|
196
|
+
},
|
197
|
+
plot_per_year_cumulative={
|
198
|
+
column.display_name: plot_year_cumulative(df, column)
|
199
|
+
for column in META_COLUMNS
|
200
|
+
},
|
201
|
+
plot_per_iso_week={
|
202
|
+
column.display_name: plot_per_iso_week(df, column)
|
203
|
+
for column in META_COLUMNS
|
204
|
+
},
|
205
|
+
heatmap_per_day={
|
206
|
+
column.display_name: heatmap_per_day(df, column)
|
207
|
+
for column in META_COLUMNS
|
208
|
+
},
|
209
|
+
)
|
210
|
+
|
211
|
+
return blueprint
|
@@ -15,19 +15,44 @@ column_distance = ColumnDescription(
|
|
15
15
|
unit="km",
|
16
16
|
format=".1f",
|
17
17
|
)
|
18
|
-
|
19
|
-
column_elevation = ColumnDescription(
|
20
|
-
name="elevation",
|
21
|
-
display_name="Elevation",
|
22
|
-
unit="m",
|
23
|
-
format=".0f",
|
24
|
-
)
|
25
18
|
column_elevation_gain = ColumnDescription(
|
26
19
|
name="elevation_gain",
|
27
20
|
display_name="Elevation Gain",
|
28
21
|
unit="m",
|
29
22
|
format=".0f",
|
30
23
|
)
|
24
|
+
column_hours = ColumnDescription(
|
25
|
+
name="hours",
|
26
|
+
display_name="Elapsed time",
|
27
|
+
unit="h",
|
28
|
+
format=".1f",
|
29
|
+
)
|
30
|
+
column_hours_moving = ColumnDescription(
|
31
|
+
name="hours_moving",
|
32
|
+
display_name="Moving time",
|
33
|
+
unit="h",
|
34
|
+
format=".1f",
|
35
|
+
)
|
36
|
+
column_calories = ColumnDescription(
|
37
|
+
name="calories",
|
38
|
+
display_name="Energy",
|
39
|
+
unit="kcal",
|
40
|
+
format=".1f",
|
41
|
+
)
|
42
|
+
column_steps = ColumnDescription(
|
43
|
+
name="steps",
|
44
|
+
display_name="Steps",
|
45
|
+
unit="1",
|
46
|
+
format=".1f",
|
47
|
+
)
|
48
|
+
META_COLUMNS = [
|
49
|
+
column_distance,
|
50
|
+
column_elevation_gain,
|
51
|
+
column_hours,
|
52
|
+
column_hours_moving,
|
53
|
+
column_calories,
|
54
|
+
column_steps,
|
55
|
+
]
|
31
56
|
|
32
57
|
column_speed = ColumnDescription(
|
33
58
|
name="speed",
|
@@ -35,3 +60,11 @@ column_speed = ColumnDescription(
|
|
35
60
|
unit="km/h",
|
36
61
|
format=".1f",
|
37
62
|
)
|
63
|
+
column_elevation = ColumnDescription(
|
64
|
+
name="elevation",
|
65
|
+
display_name="Elevation",
|
66
|
+
unit="m",
|
67
|
+
format=".0f",
|
68
|
+
)
|
69
|
+
|
70
|
+
TIME_SERIES_COLUMNS = [column_speed, column_elevation]
|
@@ -3,13 +3,13 @@
|
|
3
3
|
"short_name": "MySite",
|
4
4
|
"icons": [
|
5
5
|
{
|
6
|
-
"src": "/static/web-app-manifest-192x192.png",
|
6
|
+
"src": "/static/favicons/web-app-manifest-192x192.png",
|
7
7
|
"sizes": "192x192",
|
8
8
|
"type": "image/png",
|
9
9
|
"purpose": "maskable"
|
10
10
|
},
|
11
11
|
{
|
12
|
-
"src": "/static/web-app-manifest-512x512.png",
|
12
|
+
"src": "/static/favicons/web-app-manifest-512x512.png",
|
13
13
|
"sizes": "512x512",
|
14
14
|
"type": "image/png",
|
15
15
|
"purpose": "maskable"
|
@@ -17,7 +17,7 @@ let map = L.map('explorer-map', {
|
|
17
17
|
zoom: 12
|
18
18
|
});
|
19
19
|
|
20
|
-
changeColor('
|
20
|
+
changeColor('default')
|
21
21
|
|
22
22
|
if (bbox) {
|
23
23
|
map.fitBounds(L.geoJSON(bbox).getBounds())
|
@@ -52,4 +52,9 @@ map.on('click', e => {
|
|
52
52
|
.openOn(map);
|
53
53
|
}
|
54
54
|
);
|
55
|
-
});
|
55
|
+
});
|
56
|
+
|
57
|
+
function downloadAs(suffix) {
|
58
|
+
bounds = map.getBounds();
|
59
|
+
window.location.href = `/explorer/${zoom}/${bounds.getNorth()}/${bounds.getEast()}/${bounds.getSouth()}/${bounds.getWest()}/${suffix}`
|
60
|
+
}
|
@@ -30,19 +30,19 @@
|
|
30
30
|
|
31
31
|
<div class="row mb-3">
|
32
32
|
<div class="col-md-4">
|
33
|
-
{{ vega_direct(
|
33
|
+
{{ vega_direct(tick_plot) }}
|
34
34
|
</div>
|
35
35
|
<div class="col-md-4">
|
36
|
-
{{ vega_direct(
|
36
|
+
{{ vega_direct(equipment_plot) }}
|
37
37
|
</div>
|
38
38
|
</div>
|
39
39
|
|
40
40
|
<div class="row mb-3">
|
41
41
|
<div class="col-md-4">
|
42
|
-
{{ vega_direct(
|
42
|
+
{{ vega_direct(distance_plot) }}
|
43
43
|
</div>
|
44
44
|
<div class="col-md-4">
|
45
|
-
{{ vega_direct(
|
45
|
+
{{ vega_direct(minutes_plot) }}
|
46
46
|
</div>
|
47
47
|
</div>
|
48
48
|
|
@@ -182,13 +182,13 @@
|
|
182
182
|
|
183
183
|
<div class="row mb-3">
|
184
184
|
<div class="col-md-4">
|
185
|
-
{{ vega_direct(
|
185
|
+
{{ vega_direct(distance_time_plot) }}
|
186
186
|
</div>
|
187
187
|
<div class="col-md-4">
|
188
|
-
{{ vega_direct(
|
188
|
+
{{ vega_direct(speed_time_plot) }}
|
189
189
|
</div>
|
190
190
|
<div class="col-md-4">
|
191
|
-
{{ vega_direct(
|
191
|
+
{{ vega_direct(speed_distribution_plot) }}
|
192
192
|
</div>
|
193
193
|
</div>
|
194
194
|
|
@@ -201,11 +201,11 @@
|
|
201
201
|
|
202
202
|
<div class="row mb-3">
|
203
203
|
<div class="col-md-4">
|
204
|
-
{{ vega_direct(
|
204
|
+
{{ vega_direct(elevation_time_plot) }}
|
205
205
|
</div>
|
206
206
|
{% if elevation_gain_cum_plot is defined %}
|
207
207
|
<div class="col-md-4">
|
208
|
-
{{ vega_direct(
|
208
|
+
{{ vega_direct(elevation_gain_cum_plot) }}
|
209
209
|
</div>
|
210
210
|
{% endif %}
|
211
211
|
</div>
|
@@ -216,11 +216,11 @@
|
|
216
216
|
|
217
217
|
<div class="row mb-3">
|
218
218
|
<div class="col-md-4">
|
219
|
-
{{ vega_direct(
|
219
|
+
{{ vega_direct(heartrate_time_plot) }}
|
220
220
|
</div>
|
221
221
|
<div class="col-md-4">
|
222
222
|
{% if heart_zones_plot is defined %}
|
223
|
-
{{ vega_direct(
|
223
|
+
{{ vega_direct(heart_zones_plot) }}
|
224
224
|
{% else %}
|
225
225
|
<p>Your activity has heart data, but this program doesn't know your maximum heart rate (or birth year) and
|
226
226
|
therefore cannot compute the heart rate zones. Go to the <a
|
@@ -235,7 +235,7 @@
|
|
235
235
|
|
236
236
|
<div class="row mb-3">
|
237
237
|
<div class="col-md-4">
|
238
|
-
{{ vega_direct(
|
238
|
+
{{ vega_direct(cadence_time_plot) }}
|
239
239
|
</div>
|
240
240
|
</div>
|
241
241
|
{% endif %}
|
@@ -54,7 +54,7 @@
|
|
54
54
|
<p>In a graphical representation, the Eddington number is the distance where the red line intersects with the
|
55
55
|
blue area.</p>
|
56
56
|
|
57
|
-
{{ vega_direct(
|
57
|
+
{{ vega_direct(logarithmic_plot) }}
|
58
58
|
</div>
|
59
59
|
</div>
|
60
60
|
|
@@ -63,7 +63,7 @@
|
|
63
63
|
|
64
64
|
<p>How did the Eddington number evolve over time?</p>
|
65
65
|
|
66
|
-
{{ vega_direct(
|
66
|
+
{{ vega_direct(eddington_number_history_plot) }}
|
67
67
|
</div>
|
68
68
|
|
69
69
|
<div class="mb-3">
|
@@ -94,7 +94,7 @@
|
|
94
94
|
|
95
95
|
<div class="row mb-3">
|
96
96
|
<div class="col-md-8">
|
97
|
-
{{ vega_direct(
|
97
|
+
{{ vega_direct(eddington_per_week_plot) }}
|
98
98
|
</div>
|
99
99
|
|
100
100
|
<div class="col-md-4">
|
@@ -76,7 +76,7 @@
|
|
76
76
|
<p>In a graphical representation, the Eddington number is the elevation gain where the red line intersects with the
|
77
77
|
blue area.</p>
|
78
78
|
|
79
|
-
{{ vega_direct(
|
79
|
+
{{ vega_direct(logarithmic_plot) }}
|
80
80
|
</div>
|
81
81
|
</div>
|
82
82
|
|
@@ -85,7 +85,7 @@
|
|
85
85
|
|
86
86
|
<p>How did the Eddington number evolve over time?</p>
|
87
87
|
|
88
|
-
{{ vega_direct(
|
88
|
+
{{ vega_direct(eddington_number_history_plot) }}
|
89
89
|
</div>
|
90
90
|
|
91
91
|
<div class="mb-3">
|
@@ -116,7 +116,7 @@
|
|
116
116
|
|
117
117
|
<div class="row mb-3">
|
118
118
|
<div class="col-md-8">
|
119
|
-
{{ vega_direct(
|
119
|
+
{{ vega_direct(eddington_per_week_plot) }}
|
120
120
|
</div>
|
121
121
|
|
122
122
|
<div class="col-md-4">
|
@@ -80,7 +80,7 @@
|
|
80
80
|
the
|
81
81
|
blue area.</p>
|
82
82
|
|
83
|
-
{{ vega_direct(
|
83
|
+
{{ vega_direct(logarithmic_plot) }}
|
84
84
|
</div>
|
85
85
|
</div>
|
86
86
|
|
@@ -89,7 +89,7 @@
|
|
89
89
|
|
90
90
|
<p>How did the Eddington number evolve over time?</p>
|
91
91
|
|
92
|
-
{{ vega_direct(
|
92
|
+
{{ vega_direct(eddington_number_history_plot) }}
|
93
93
|
</div>
|
94
94
|
|
95
95
|
<div class="mb-3">
|
@@ -120,7 +120,7 @@
|
|
120
120
|
|
121
121
|
<div class="row mb-3">
|
122
122
|
<div class="col-md-8">
|
123
|
-
{{ vega_direct(
|
123
|
+
{{ vega_direct(eddington_per_week_plot) }}
|
124
124
|
</div>
|
125
125
|
|
126
126
|
<div class="col-md-4">
|