geo-activity-playground 0.40.0__py3-none-any.whl → 0.41.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.
Files changed (30) hide show
  1. geo_activity_playground/alembic/versions/38882503dc7c_add_tags_to_activities.py +70 -0
  2. geo_activity_playground/alembic/versions/script.py.mako +0 -6
  3. geo_activity_playground/core/activities.py +17 -30
  4. geo_activity_playground/core/datamodel.py +83 -2
  5. geo_activity_playground/core/test_datamodel.py +14 -1
  6. geo_activity_playground/importers/strava_checkout.py +2 -5
  7. geo_activity_playground/webui/app.py +6 -2
  8. geo_activity_playground/webui/blueprints/activity_blueprint.py +20 -3
  9. geo_activity_playground/webui/blueprints/bubble_chart_blueprint.py +50 -25
  10. geo_activity_playground/webui/blueprints/calendar_blueprint.py +12 -4
  11. geo_activity_playground/webui/blueprints/eddington_blueprints.py +253 -0
  12. geo_activity_playground/webui/blueprints/entry_views.py +30 -15
  13. geo_activity_playground/webui/blueprints/explorer_blueprint.py +83 -9
  14. geo_activity_playground/webui/blueprints/summary_blueprint.py +102 -42
  15. geo_activity_playground/webui/columns.py +37 -0
  16. geo_activity_playground/webui/templates/activity/show.html.j2 +15 -4
  17. geo_activity_playground/webui/templates/bubble_chart/index.html.j2 +24 -8
  18. geo_activity_playground/webui/templates/eddington/elevation_gain.html.j2 +150 -0
  19. geo_activity_playground/webui/templates/elevation_eddington/index.html.j2 +150 -0
  20. geo_activity_playground/webui/templates/explorer/server-side.html.j2 +72 -0
  21. geo_activity_playground/webui/templates/home.html.j2 +14 -5
  22. geo_activity_playground/webui/templates/page.html.j2 +10 -1
  23. geo_activity_playground/webui/templates/summary/index.html.j2 +91 -2
  24. {geo_activity_playground-0.40.0.dist-info → geo_activity_playground-0.41.0.dist-info}/METADATA +1 -1
  25. {geo_activity_playground-0.40.0.dist-info → geo_activity_playground-0.41.0.dist-info}/RECORD +29 -24
  26. geo_activity_playground/webui/blueprints/eddington_blueprint.py +0 -194
  27. /geo_activity_playground/webui/templates/eddington/{index.html.j2 → distance.html.j2} +0 -0
  28. {geo_activity_playground-0.40.0.dist-info → geo_activity_playground-0.41.0.dist-info}/LICENSE +0 -0
  29. {geo_activity_playground-0.40.0.dist-info → geo_activity_playground-0.41.0.dist-info}/WHEEL +0 -0
  30. {geo_activity_playground-0.40.0.dist-info → geo_activity_playground-0.41.0.dist-info}/entry_points.txt +0 -0
@@ -15,6 +15,9 @@ from ...core.datamodel import DB
15
15
  from ...core.datamodel import PlotSpec
16
16
  from ...core.meta_search import apply_search_query
17
17
  from ...core.parametric_plot import make_parametric_plot
18
+ from ..columns import column_distance
19
+ from ..columns import column_elevation_gain
20
+ from ..columns import ColumnDescription
18
21
  from ..plot_util import make_kind_scale
19
22
  from ..search_util import search_query_from_form
20
23
  from ..search_util import SearchQueryHistory
@@ -36,25 +39,38 @@ def make_summary_blueprint(
36
39
  kind_scale = make_kind_scale(repository.meta, config)
37
40
  df = activities
38
41
 
39
- year_kind_total = (
40
- df[["year", "kind", "distance_km", "hours"]]
41
- .groupby(["year", "kind"])
42
- .sum()
43
- .reset_index()
44
- )
45
-
46
42
  nominations = nominate_activities(df)
47
43
 
48
44
  return render_template(
49
45
  "summary/index.html.j2",
50
- plot_distance_heatmaps=plot_distance_heatmaps(df, config),
51
- plot_monthly_distance=plot_monthly_distance(df, kind_scale),
52
- plot_yearly_distance=plot_yearly_distance(year_kind_total, kind_scale),
53
- plot_year_cumulative=plot_year_cumulative(df),
54
- tabulate_year_kind_mean=tabulate_year_kind_mean(df)
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)
55
63
  .reset_index()
56
64
  .to_dict(orient="split"),
57
- plot_weekly_distance=plot_weekly_distance(df, kind_scale),
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
+ ),
58
74
  nominations=[
59
75
  (
60
76
  repository.get_activity_by_id(activity_id),
@@ -109,11 +125,13 @@ def _nominate_activities_inner(
109
125
  nominations[i].append(f"{title}{title_suffix}: {format_applied}")
110
126
 
111
127
 
112
- def plot_distance_heatmaps(meta: pd.DataFrame, config: Config) -> dict[int, str]:
128
+ def plot_heatmaps(
129
+ meta: pd.DataFrame, column: ColumnDescription, config: Config
130
+ ) -> dict[int, str]:
113
131
  return {
114
132
  year: alt.Chart(
115
133
  meta.loc[(meta["year"] == year)],
116
- title="Daily Distance Heatmap",
134
+ title=f"Daily {column.display_name} Heatmap",
117
135
  )
118
136
  .mark_rect()
119
137
  .encode(
@@ -124,15 +142,17 @@ def plot_distance_heatmaps(meta: pd.DataFrame, config: Config) -> dict[int, str]
124
142
  title="Year and month",
125
143
  ),
126
144
  alt.Color(
127
- "sum(distance_km)",
145
+ f"sum({column.name})",
128
146
  scale=alt.Scale(scheme=config.color_scheme_for_counts),
129
147
  ),
130
148
  [
131
149
  alt.Tooltip("yearmonthdate(start)", title="Date"),
132
150
  alt.Tooltip(
133
- "sum(distance_km)", format=".1f", title="Total distance / km"
151
+ f"sum({column.name})",
152
+ format=column.format,
153
+ title=f"Total {column.display_name} / {column.unit}",
134
154
  ),
135
- alt.Tooltip("count(distance_km)", title="Number of activities"),
155
+ alt.Tooltip(f"count({column.name})", title="Number of activities"),
136
156
  ],
137
157
  )
138
158
  .to_json(format="vega")
@@ -140,7 +160,9 @@ def plot_distance_heatmaps(meta: pd.DataFrame, config: Config) -> dict[int, str]
140
160
  }
141
161
 
142
162
 
143
- def plot_monthly_distance(meta: pd.DataFrame, kind_scale: alt.Scale) -> str:
163
+ def plot_monthly_sums(
164
+ meta: pd.DataFrame, column: ColumnDescription, kind_scale: alt.Scale
165
+ ) -> str:
144
166
  return (
145
167
  alt.Chart(
146
168
  meta.loc[
@@ -151,20 +173,26 @@ def plot_monthly_distance(meta: pd.DataFrame, kind_scale: alt.Scale) -> str:
151
173
  )
152
174
  )
153
175
  ],
154
- title="Monthly Distance",
176
+ title=f"Monthly {column.display_name}",
155
177
  )
156
178
  .mark_bar()
157
179
  .encode(
158
180
  alt.X("month(start)", title="Month"),
159
- alt.Y("sum(distance_km)", title="Distance / km"),
181
+ alt.Y(
182
+ f"sum({column.name})",
183
+ title=f"{column.display_name} / {column.unit}",
184
+ ),
160
185
  alt.Color("kind", scale=kind_scale, title="Kind"),
161
186
  alt.Column("year(start):O", title="Year"),
162
187
  [
163
- alt.Tooltip("yearmonthdate(start)", title="Date"),
188
+ alt.Tooltip("yearmonth(start)", title="Year and Month"),
189
+ alt.Tooltip("kind", title="Kind"),
164
190
  alt.Tooltip(
165
- "sum(distance_km)", format=".1f", title="Total distance / km"
191
+ f"sum({column.name})",
192
+ format=column.format,
193
+ title=f"Total {column.display_name} / {column.unit}",
166
194
  ),
167
- alt.Tooltip("count(distance_km)", title="Number of activities"),
195
+ alt.Tooltip(f"count({column.name})", title="Number of activities"),
168
196
  ],
169
197
  )
170
198
  .resolve_axis(x="independent")
@@ -172,31 +200,47 @@ def plot_monthly_distance(meta: pd.DataFrame, kind_scale: alt.Scale) -> str:
172
200
  )
173
201
 
174
202
 
175
- def plot_yearly_distance(year_kind_total: pd.DataFrame, kind_scale: alt.Scale) -> str:
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
+
176
213
  return (
177
- alt.Chart(year_kind_total, title="Total Distance per Year")
214
+ alt.Chart(year_kind_total, title=f"Total {column.display_name} per Year")
178
215
  .mark_bar()
179
216
  .encode(
180
217
  alt.X("year:O", title="Year"),
181
- alt.Y("distance_km", title="Distance / km"),
218
+ alt.Y(column.name, title=f"{column.display_name} / {column.unit}"),
182
219
  alt.Color("kind", scale=kind_scale, title="Kind"),
183
220
  [
184
221
  alt.Tooltip("year:O", title="Year"),
185
222
  alt.Tooltip("kind", title="Kind"),
186
- alt.Tooltip("distance_km", title="Distance / km", format=".1f"),
223
+ alt.Tooltip(
224
+ column.name,
225
+ title=f"{column.display_name} / {column.unit}",
226
+ format=column.format,
227
+ ),
187
228
  ],
188
229
  )
189
230
  .to_json(format="vega")
190
231
  )
191
232
 
192
233
 
193
- def plot_year_cumulative(df: pd.DataFrame) -> str:
234
+ def plot_year_cumulative(df: pd.DataFrame, column: ColumnDescription) -> str:
194
235
  year_cumulative = (
195
- df[["iso_year", "week", "distance_km"]]
236
+ df[["iso_year", "week", column.name]]
196
237
  .groupby("iso_year")
197
238
  .apply(
198
239
  lambda group: pd.DataFrame(
199
- {"week": group["week"], "distance_km": group["distance_km"].cumsum()}
240
+ {
241
+ "week": group["week"],
242
+ column.name: group[column.name].cumsum(),
243
+ }
200
244
  ),
201
245
  include_groups=False,
202
246
  )
@@ -204,16 +248,24 @@ def plot_year_cumulative(df: pd.DataFrame) -> str:
204
248
  )
205
249
 
206
250
  return (
207
- alt.Chart(year_cumulative, width=500, title="Cumultative Distance per Year")
251
+ alt.Chart(
252
+ year_cumulative,
253
+ width=500,
254
+ title=f"Cumulative {column.display_name} per Year",
255
+ )
208
256
  .mark_line()
209
257
  .encode(
210
258
  alt.X("week", title="Week"),
211
- alt.Y("distance_km", title="Distance / km"),
259
+ alt.Y(column.name, title=f"{column.display_name} / {column.unit}"),
212
260
  alt.Color("iso_year:N", title="Year"),
213
261
  [
214
262
  alt.Tooltip("week", title="Week"),
215
263
  alt.Tooltip("iso_year:N", title="Year"),
216
- alt.Tooltip("distance_km", title="Distance / km", format=".1f"),
264
+ alt.Tooltip(
265
+ column.name,
266
+ title=f"{column.display_name} / {column.unit}",
267
+ format=column.format,
268
+ ),
217
269
  ],
218
270
  )
219
271
  .interactive()
@@ -221,24 +273,28 @@ def plot_year_cumulative(df: pd.DataFrame) -> str:
221
273
  )
222
274
 
223
275
 
224
- def tabulate_year_kind_mean(df: pd.DataFrame) -> pd.DataFrame:
276
+ def tabulate_year_kind_mean(
277
+ df: pd.DataFrame, column: ColumnDescription
278
+ ) -> pd.DataFrame:
225
279
  year_kind_mean = (
226
- df[["year", "kind", "distance_km", "hours"]]
280
+ df[["year", "kind", column.name, "hours"]]
227
281
  .groupby(["year", "kind"])
228
282
  .mean()
229
283
  .reset_index()
230
284
  )
231
285
 
232
286
  year_kind_mean_distance = year_kind_mean.pivot(
233
- index="year", columns="kind", values="distance_km"
287
+ index="year", columns="kind", values=column.name
234
288
  )
235
289
 
236
290
  return year_kind_mean_distance
237
291
 
238
292
 
239
- def plot_weekly_distance(df: pd.DataFrame, kind_scale: alt.Scale) -> str:
293
+ def plot_weekly_sums(
294
+ df: pd.DataFrame, column: ColumnDescription, kind_scale: alt.Scale
295
+ ) -> str:
240
296
  week_kind_total_distance = (
241
- df[["iso_year", "week", "kind", "distance_km"]]
297
+ df[["iso_year", "week", "kind", column.name]]
242
298
  .groupby(["iso_year", "week", "kind"])
243
299
  .sum()
244
300
  .reset_index()
@@ -260,17 +316,21 @@ def plot_weekly_distance(df: pd.DataFrame, kind_scale: alt.Scale) -> str:
260
316
  | (week_kind_total_distance["iso_year"] == last_year - 1)
261
317
  & (week_kind_total_distance["week"] >= last_week)
262
318
  ],
263
- title="Weekly Distance",
319
+ title=f"Weekly {column.display_name}",
264
320
  )
265
321
  .mark_bar()
266
322
  .encode(
267
323
  alt.X("year_week", title="Year and Week"),
268
- alt.Y("distance_km", title="Distance / km"),
324
+ alt.Y(column.name, title=f"{column.display_name} / {column.unit}"),
269
325
  alt.Color("kind", scale=kind_scale, title="Kind"),
270
326
  [
271
327
  alt.Tooltip("year_week", title="Year and Week"),
272
328
  alt.Tooltip("kind", title="Kind"),
273
- alt.Tooltip("distance_km", title="Distance / km", format=".1f"),
329
+ alt.Tooltip(
330
+ column.name,
331
+ title=f"{column.display_name} / {column.unit}",
332
+ format=column.format,
333
+ ),
274
334
  ],
275
335
  )
276
336
  .to_json(format="vega")
@@ -0,0 +1,37 @@
1
+ import dataclasses
2
+
3
+
4
+ @dataclasses.dataclass
5
+ class ColumnDescription:
6
+ name: str
7
+ display_name: str
8
+ unit: str
9
+ format: str
10
+
11
+
12
+ column_distance = ColumnDescription(
13
+ name="distance_km",
14
+ display_name="Distance",
15
+ unit="km",
16
+ format=".1f",
17
+ )
18
+
19
+ column_elevation = ColumnDescription(
20
+ name="elevation",
21
+ display_name="Elevation",
22
+ unit="m",
23
+ format=".0f",
24
+ )
25
+ column_elevation_gain = ColumnDescription(
26
+ name="elevation_gain",
27
+ display_name="Elevation Gain",
28
+ unit="m",
29
+ format=".0f",
30
+ )
31
+
32
+ column_speed = ColumnDescription(
33
+ name="speed",
34
+ display_name="Speed",
35
+ unit="km/h",
36
+ format=".1f",
37
+ )
@@ -49,7 +49,7 @@
49
49
 
50
50
  {% if activity.elevation_gain %}
51
51
  <dt>Elevation gain</dt>
52
- <dd>{{ activity.elevation_gain|round(0) }} m</dd>
52
+ <dd>{{ activity.elevation_gain|round(0)|int }} m</dd>
53
53
  {% endif %}
54
54
 
55
55
  {% if activity.equipment %}
@@ -105,10 +105,21 @@
105
105
  </style>
106
106
 
107
107
  <div>
108
- {% for speed, color in speed_color_bar.colors %}
109
- <span class="colorbar" style="width: 15px; background-color: {{ color }}">{{ speed }}</span>
108
+ {% for value, color in line_color_bar.colors %}
109
+ <span class="colorbar" style="width: 15px; background-color: {{ color }}">{{ value }}</span>
110
110
  {% endfor %}
111
- km/h
111
+ {{ line_color_columns_avail[line_color_column].unit }}
112
+ </div>
113
+
114
+ <div class="mb-3" style="padding-top: 10px;">
115
+ <form method="GET">
116
+ <label class="form-label">Line Color by</label>
117
+ <select class="form-select" aria-label="Line Color by" name="line_color_column" onchange="this.form.submit()">
118
+ {% for name, column in line_color_columns_avail.items() %}
119
+ <option {% if name == line_color_column %} selected {% endif %} value="{{ name }}">{{ column.display_name }}</option>
120
+ {% endfor %}
121
+ </select>
122
+ </form>
112
123
  </div>
113
124
  </div>
114
125
  </div>
@@ -1,17 +1,33 @@
1
- <!-- filepath: /home/michael/Nextcloud/Python/Geoactivity-Fork/geo-activity-playground/geo_activity_playground/webui/templates/bubble_chart/index.html.j2 -->
2
1
  {% extends "page.html.j2" %}
3
2
 
4
3
  {% block container %}
4
+
5
+ <div class="row mb-3">
6
+ <div class="col">
7
+ <h1>Bubble Chart: Distance</h1>
8
+ <div id="bubble-chart-distance"></div>
9
+ <script>
10
+ const chartSpecDistance = {{ bubble_chart_distance | safe }};
11
+ vegaEmbed('#bubble-chart-distance', chartSpecDistance).then((result) => {
12
+ // Add a click event listener to the chart
13
+ result.view.addEventListener('click', (event, item) => {
14
+ if (item && item.datum && item.datum.activity_url) {
15
+ // Redirect to the activity URL
16
+ window.location.href = item.datum.activity_url;
17
+ }
18
+ });
19
+ }).catch(console.error);
20
+ </script>
21
+ </div>
22
+ </div>
23
+
5
24
  <div class="row mb-3">
6
25
  <div class="col">
7
- <h1>Bubble Chart</h1>
8
- <div id="bubble-chart"></div>
9
- <script src="https://cdn.jsdelivr.net/npm/vega@5"></script>
10
- <script src="https://cdn.jsdelivr.net/npm/vega-lite@5"></script>
11
- <script src="https://cdn.jsdelivr.net/npm/vega-embed@6"></script>
26
+ <h1>Bubble Chart: Elevation Gain</h1>
27
+ <div id="bubble-chart-elevation-gain"></div>
12
28
  <script>
13
- const chartSpec = {{ bubble_chart | safe }};
14
- vegaEmbed('#bubble-chart', chartSpec).then((result) => {
29
+ const chartSpecElevationGain = {{ bubble_chart_elevation_gain | safe }};
30
+ vegaEmbed('#bubble-chart-elevation-gain', chartSpecElevationGain).then((result) => {
15
31
  // Add a click event listener to the chart
16
32
  result.view.addEventListener('click', (event, item) => {
17
33
  if (item && item.datum && item.datum.activity_url) {
@@ -0,0 +1,150 @@
1
+ {% extends "page.html.j2" %}
2
+ {% from "search_form.html.j2" import search_form %}
3
+
4
+ {% block container %}
5
+ <h1 class="mb-3">Eddington Number for Elevation Gain</h1>
6
+
7
+ <div class="mb-3">
8
+ {{ search_form(query, equipments_avail, kinds_avail, search_query_favorites, search_query_last, request_url) }}
9
+ </div>
10
+
11
+ <div class="row mb-3">
12
+ <div class="col-md-4">
13
+ <form method="GET">
14
+ <div class="mb-3">
15
+ <label class="form-label">Divisor</label>
16
+ <select class="form-select" aria-label="Divisor" name="eddington_divisor" onchange="this.form.submit()">
17
+ {% for dv in divisor_values_avail %}
18
+ <option {% if dv==eddington_divisor %} selected {% endif %}>{{ dv }}</option>
19
+ {% endfor %}
20
+ {% for key, value in query.items() %}
21
+ {% if value and key != "active" %}
22
+ {% if value is string %}
23
+ <input type="hidden" name="{{ key }}" value="{{ value }}"/>
24
+ {% else %}
25
+ {% for v in value %}
26
+ <input type="hidden" name="{{ key }}" value="{{ v }}"/>
27
+ {% endfor %}
28
+ {% endif %}
29
+ {% endif %}
30
+ {% endfor %}
31
+ </select>
32
+ </div>
33
+ </form>
34
+
35
+ <h2>Definition</h2>
36
+
37
+ <p>Your Eddington number with divisor {{ eddington_divisor }} is <b>{{ eddington_number }}</b>.</p>
38
+
39
+ <p>That means that on {{ (eddington_number / eddington_divisor) | int }} separate days you
40
+ have recorded activities with an elevation gain of more than {{ eddington_number }} m. Going high is one thing, going often is
41
+ another. But going high often is hard. Also if you increment the Eddington number, all days with less
42
+ elevation gain will not count any more. It becomes increasingly hard to increment the Eddington number because you
43
+ don't only need to achieve a higher count, but all flatter activities don't count towards the bigger number.
44
+ </p>
45
+ </div>
46
+
47
+ <div class="col-md-8">
48
+ <h2>Missing days</h2>
49
+
50
+ <p>How many more days do you need to increment your Eddington number?</p>
51
+
52
+ <table class="table">
53
+ <thead>
54
+ <tr>
55
+ <th>Elevation Gain</th>
56
+ <th>Count</th>
57
+ <th>Missing days</th>
58
+ </tr>
59
+ </thead>
60
+ <tbody>
61
+ {% for row in eddington_table %}
62
+ <tr>
63
+ <td>{{ row['elevation_gain'] }}</td>
64
+ <td>{{ row['total'] }}</td>
65
+ <td>{{ row['missing'] }}</td>
66
+ </tr>
67
+ {% endfor %}
68
+ </tbody>
69
+ </table>
70
+ </div>
71
+ </div>
72
+
73
+ <div class="row mb-1">
74
+ <div class="col">
75
+ <h2>Plot</h2>
76
+
77
+ <p>In a graphical representation, the Eddington number is the elevation gain where the red line intersects with the
78
+ blue area.</p>
79
+
80
+ {{ vega_direct("logarithmic_plot", logarithmic_plot) }}
81
+ </div>
82
+ </div>
83
+
84
+ <div class="mb-3">
85
+ <h2 class="mb-3">Eddington number history</h2>
86
+
87
+ <p>How did the Eddington number evolve over time?</p>
88
+
89
+ {{ vega_direct("eddington_number_history_plot", eddington_number_history_plot) }}
90
+ </div>
91
+
92
+ <div class="mb-3">
93
+ <h2 class="mb-3">Yearly Eddington number</h2>
94
+
95
+ <p>If we only consider the activities within one calendar year for the Eddington number, we get the following:</p>
96
+
97
+ <table class="table">
98
+ <thead>
99
+ <tr>
100
+ <th>Year</th>
101
+ <th>Eddington number</th>
102
+ </tr>
103
+ </thead>
104
+ <tbody>
105
+ {% for year, eddington in yearly_eddington.items() %}
106
+ <tr>
107
+ <td>{{ year }}</td>
108
+ <td>{{ eddington }}</td>
109
+ </tr>
110
+ {% endfor %}
111
+ </tbody>
112
+ </table>
113
+ </div>
114
+
115
+
116
+ <h2 class="mb-3">Eddington number per Week</h2>
117
+
118
+ <div class="row mb-3">
119
+ <div class="col-md-8">
120
+ {{ vega_direct("eddington_per_week_plot", eddington_per_week_plot) }}
121
+ </div>
122
+
123
+ <div class="col-md-4">
124
+ <table class="table">
125
+ <thead>
126
+ <tr>
127
+ <th>Elevation Gain</th>
128
+ <th>Count</th>
129
+ <th>Missing weeks</th>
130
+ </tr>
131
+ </thead>
132
+ <tbody>
133
+ {% for row in eddington_table_weeks %}
134
+ <tr>
135
+ <td>{{ row['elevation_gain'] }}</td>
136
+ <td>{{ row['total'] }}</td>
137
+ <td>{{ row['missing'] }}</td>
138
+ </tr>
139
+ {% endfor %}
140
+ </tbody>
141
+ </table>
142
+ </div>
143
+ </div>
144
+
145
+ <div class="row mb-1">
146
+
147
+ </div>
148
+
149
+
150
+ {% endblock %}