Skip to content

Commit 665a9bf

Browse files
authored
Merge pull request #55 from veloek/stats-average
Changed the summary part of the statistics page.
2 parents 6af911d + 9e0e3de commit 665a9bf

File tree

3 files changed

+255
-66
lines changed

3 files changed

+255
-66
lines changed

Tevling/Pages/Statistics.razor

Lines changed: 114 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<label>Months:</label>
77
<InputSelect @bind-Value="@NumberOfMonthsToReview" @oninput="DrawChart" class="mb-2">
8-
@for (int i = 3; i <= 36; i+=3)
8+
@for (int i = 3; i <= 36; i += 3)
99
{
1010
<option value="@i">@i</option>
1111
}
@@ -17,40 +17,122 @@
1717
OnButtonSelected="@DrawChart">
1818
</ButtonGroup>
1919

20-
<div class="d-flex flex-wrap p-2">
20+
<div class="d-flex flex-wrap p-2 mb-5">
2121
<canvas id="TheChart"></canvas>
2222
</div>
2323

24-
<h2 class="title mb-5">Summary</h2>
25-
<p>The last <b>@NumberOfMonthsToReview</b> month(s) you've:</p>
26-
27-
@switch (Measurement)
28-
{
29-
case ChallengeMeasurement.Distance:
30-
@foreach (KeyValuePair<string, float[]> kvp in Distances)
24+
<h2>The last <b>@NumberOfMonthsToReview</b> month(s) you've:</h2>
25+
<div>
26+
<table style="width: 100%">
27+
<thead>
28+
<tr>
29+
<th scope="col">Activity</th>
30+
<th scope="col">
31+
@switch (Measurement)
32+
{
33+
case ChallengeMeasurement.Distance:
34+
<span>Traversed</span>
35+
break;
36+
case ChallengeMeasurement.Elevation:
37+
<span>Climbed</span>
38+
break;
39+
case ChallengeMeasurement.Time:
40+
<span>Spent</span>
41+
break;
42+
default:
43+
throw new ArgumentOutOfRangeException();
44+
}
45+
</th>
46+
<th scope="col">Averaging</th>
47+
<th scope="col">Current Month</th>
48+
</tr>
49+
</thead>
50+
<tbody>
51+
@switch (Measurement)
3152
{
32-
<p>- Traversed <b>@Math.Round(kvp.Value.Sum(), 1)</b>km by @kvp.Key</p>
53+
case ChallengeMeasurement.Distance:
54+
@foreach (Stats stats in Distances)
55+
{
56+
<tr>
57+
<td data-label="Activity">
58+
@stats.Type
59+
</td>
60+
<td data-label="Traversed">
61+
@stats.LastMonthsTotal km
62+
</td>
63+
<td data-label="Averaging">
64+
@stats.LastMonthsAverage km
65+
</td>
66+
<td data-label="Current Month">
67+
@Math.Round(stats.ThisMonth, 1) km
68+
<b>(@Math.Round(stats.CurrentMonthComparedToAverage(), 1)%</b> @((MarkupString)stats.IncreaseVsDecrease()))
69+
</td>
70+
</tr>
71+
}
72+
break;
73+
case ChallengeMeasurement.Elevation:
74+
@foreach (Stats stats in Elevations)
75+
{
76+
<tr>
77+
<td data-label="Activity">
78+
@stats.Type
79+
</td>
80+
<td data-label="Climbed">
81+
@stats.LastMonthsTotal m
82+
</td>
83+
<td data-label="Averaging">
84+
@stats.LastMonthsAverage m
85+
</td>
86+
<td data-label="Current Month">
87+
@Math.Round(stats.ThisMonth, 1) m
88+
<b>(@Math.Round(stats.CurrentMonthComparedToAverage(), 1)%</b> @((MarkupString)stats.IncreaseVsDecrease()))
89+
</td>
90+
</tr>
91+
}
92+
break;
93+
case ChallengeMeasurement.Time:
94+
@foreach (Stats stats in Durations)
95+
{
96+
<tr>
97+
<td data-label="Activity">
98+
@stats.Type
99+
</td>
100+
<td data-label="Spent">
101+
@stats.LastMonthsTotal h
102+
</td>
103+
<td data-label="Averaging">
104+
@stats.LastMonthsAverage h
105+
</td>
106+
<td data-label="Current Month">
107+
@Math.Round(stats.ThisMonth, 1) h
108+
<b>(@Math.Round(stats.CurrentMonthComparedToAverage(), 1)%</b> @((MarkupString)stats.IncreaseVsDecrease()))
109+
</td>
110+
</tr>
111+
}
112+
break;
113+
default:
114+
throw new ArgumentOutOfRangeException();
33115
}
34-
35-
<p>A total of <b>@Math.Round(Distances.Sum(kvp => kvp.Value.Sum()), 1)</b>km</p>
36-
break;
37-
case ChallengeMeasurement.Elevation:
38-
@foreach (KeyValuePair<string, float[]> kvp in Elevations)
39-
{
40-
<p>- Ascended <b>@Math.Round(kvp.Value.Sum(), 1)</b>m by @kvp.Key</p>
41-
}
42-
43-
<p>A total of <b>@Math.Round(Elevations.Sum(kvp => kvp.Value.Sum()), 1)</b>m</p>
44-
break;
45-
case ChallengeMeasurement.Time:
46-
@foreach (KeyValuePair<string, float[]> kvp in Durations)
47-
{
48-
<p>- Spent <b>@Math.Round(kvp.Value.Sum(), 1)</b> hours on @kvp.Key</p>
49-
}
50-
51-
<p>A total of <b>@Math.Round(Durations.Sum(kvp => kvp.Value.Sum()), 1)</b> hours</p>
52-
break;
53-
default:
54-
throw new ArgumentOutOfRangeException();
55-
}
116+
</tbody>
117+
</table>
118+
<div class="mt-2">
119+
<h4><u>A total of <b>
120+
@switch (Measurement)
121+
{
122+
case ChallengeMeasurement.Distance:
123+
<span>@Math.Round(Distances.Sum(stats => stats.LastMonthsTotal), 1) km</span>
124+
break;
125+
case ChallengeMeasurement.Elevation:
126+
<span>@Math.Round(Elevations.Sum(stats => stats.LastMonthsTotal), 1) m</span>
127+
break;
128+
case ChallengeMeasurement.Time:
129+
<span>@Math.Round(Durations.Sum(stats => stats.LastMonthsTotal), 1) h</span>
130+
break;
131+
default:
132+
throw new ArgumentOutOfRangeException();
133+
}
134+
</b></u></h4>
135+
</div>
136+
</div>
137+
56138

Tevling/Pages/Statistics.razor.cs

Lines changed: 88 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,49 @@
1-
using System.Globalization;
21
using Microsoft.JSInterop;
32

43
namespace Tevling.Pages;
54

65
public partial class Statistics : ComponentBase, IAsyncDisposable
76
{
7+
private record Stats
8+
{
9+
public required string Type { get; init; }
10+
public required float[] LastMonthsAggregate { get; init; }
11+
public double LastMonthsAverage => Math.Round(LastMonthsAggregate.Average(), 1);
12+
public double LastMonthsTotal => Math.Round(LastMonthsAggregate.Sum(), 1);
13+
14+
public float ThisMonth => LastMonthsAggregate[^1];
15+
16+
public double CurrentMonthComparedToAverage()
17+
{
18+
if (LastMonthsAverage == 0)
19+
{
20+
return 100;
21+
}
22+
23+
double difference =
24+
100 * (Math.Round(ThisMonth, 1) / LastMonthsAverage);
25+
if (difference > 100)
26+
{
27+
return difference - 100;
28+
}
29+
30+
return 100 - difference;
31+
}
32+
33+
public string IncreaseVsDecrease()
34+
{
35+
if (LastMonthsAverage == 0)
36+
{
37+
return "<span style='color:green'>increase</span>";
38+
}
39+
40+
return Math.Round(ThisMonth, 1) / LastMonthsAverage > 1
41+
? "<span style='color:green'>increase</span>"
42+
: "<span style='color:red'>decrease</span>";
43+
}
44+
}
45+
46+
847
[Inject] private IJSRuntime Js { get; set; } = null!;
948
[Inject] private IAuthenticationService AuthenticationService { get; set; } = null!;
1049
[Inject] private IActivityService ActivityService { get; set; } = null!;
@@ -15,39 +54,45 @@ public partial class Statistics : ComponentBase, IAsyncDisposable
1554

1655
private int NumberOfMonthsToReview { get; set; } = 3;
1756
private ChallengeMeasurement Measurement { get; set; } = ChallengeMeasurement.Distance;
18-
private Dictionary<string, float[]> Distances { get; set; } = [];
19-
private Dictionary<string, float[]> Elevations { get; set; } = [];
20-
private Dictionary<string, float[]> Durations { get; set; } = [];
57+
private IReadOnlyList<Stats> Distances { get; set; } = [];
58+
private IReadOnlyList<Stats> Elevations { get; set; } = [];
59+
private IReadOnlyList<Stats> Durations { get; set; } = [];
2160

2261
protected override async Task OnInitializedAsync()
2362
{
2463
_athlete = await AuthenticationService.GetCurrentAthleteAsync();
2564
await UpdateMeasurementData();
2665
}
2766

28-
private Dictionary<string, float[]> GetAggregatedMeasurementData(Func<Activity, float> selector, int monthCount = 3)
67+
private List<Stats> GetAggregatedMeasurementData(Func<Activity, float> selector, int monthCount = 3)
2968
{
3069
DateTimeOffset now = DateTimeOffset.Now;
3170

32-
Dictionary<string, float[]> aggregatedData = _activities
33-
.GroupBy(a => ActivityTypeExt.ToString(a.Details.Type))
34-
.ToDictionary(
35-
g => g.Key.ToString(),
36-
g => Enumerable.Range(-monthCount + 1, monthCount)
37-
.Select(m =>
71+
return
72+
[
73+
.. _activities
74+
.GroupBy(a => ActivityTypeExt.ToString(a.Details.Type))
75+
.ToDictionary(
76+
g => g.Key.ToString(),
77+
g => Enumerable.Range(-monthCount + 1, monthCount)
78+
.Select(m =>
79+
{
80+
int month = now.AddMonths(m).Month;
81+
int year = now.AddMonths(m).Year;
82+
return g
83+
.Where(a => a.Details.StartDate.Month == month && a.Details.StartDate.Year == year)
84+
.Sum(selector);
85+
})
86+
.ToArray()
87+
)
88+
.Where(d => d.Value.Any(v => v > 0))
89+
.Select(kvp => new Stats
3890
{
39-
int month = now.AddMonths(m).Month;
40-
int year = now.AddMonths(m).Year;
41-
return g
42-
.Where(a => a.Details.StartDate.Month == month && a.Details.StartDate.Year == year)
43-
.Sum(selector);
44-
})
45-
.ToArray()
46-
)
47-
.Where(d => d.Value.Any(v => v > 0))
48-
.ToDictionary();
49-
50-
return aggregatedData;
91+
Type = kvp.Key,
92+
LastMonthsAggregate = kvp.Value,
93+
}
94+
),
95+
];
5196
}
5297

5398
private static string[] CreateMonthArray(int monthCount)
@@ -79,14 +124,23 @@ private async Task UpdateMeasurementData()
79124
false,
80125
DateTimeOffset.Now.AddMonths(-NumberOfMonthsToReview + 1).ToFirstOfTheMonth());
81126
_activities = await ActivityService.GetActivitiesAsync(filter);
82-
83-
Distances = GetAggregatedMeasurementData(
84-
a => a.Details.DistanceInMeters / 1000,
85-
NumberOfMonthsToReview);
86-
Elevations = GetAggregatedMeasurementData(a => a.Details.TotalElevationGain, NumberOfMonthsToReview);
87-
Durations = GetAggregatedMeasurementData(
88-
a => (float)a.Details.MovingTimeInSeconds / 3600,
89-
NumberOfMonthsToReview);
127+
128+
Distances =
129+
[
130+
.. GetAggregatedMeasurementData(
131+
a => a.Details.DistanceInMeters / 1000,
132+
NumberOfMonthsToReview),
133+
];
134+
Elevations =
135+
[
136+
.. GetAggregatedMeasurementData(a => a.Details.TotalElevationGain, NumberOfMonthsToReview),
137+
];
138+
Durations =
139+
[
140+
.. GetAggregatedMeasurementData(
141+
a => (float)a.Details.MovingTimeInSeconds / 3600,
142+
NumberOfMonthsToReview),
143+
];
90144
}
91145

92146
private async Task DrawChart()
@@ -102,7 +156,7 @@ private async Task DrawChart()
102156
case ChallengeMeasurement.Distance:
103157
await _module.InvokeVoidAsync(
104158
"drawChart",
105-
Distances,
159+
Distances.ToDictionary(stat => stat.Type, stat => stat.LastMonthsAggregate),
106160
months,
107161
"TheChart",
108162
"Total Distance [km]",
@@ -111,7 +165,7 @@ await _module.InvokeVoidAsync(
111165
case ChallengeMeasurement.Elevation:
112166
await _module.InvokeVoidAsync(
113167
"drawChart",
114-
Elevations,
168+
Elevations.ToDictionary(stat => stat.Type, stat => stat.LastMonthsAggregate),
115169
months,
116170
"TheChart",
117171
"Total Elevation [m]",
@@ -120,7 +174,7 @@ await _module.InvokeVoidAsync(
120174
case ChallengeMeasurement.Time:
121175
await _module.InvokeVoidAsync(
122176
"drawChart",
123-
Durations,
177+
Durations.ToDictionary(stat => stat.Type, stat => stat.LastMonthsAggregate),
124178
months,
125179
"TheChart",
126180
"Total Time [h]",

Tevling/Pages/Statistics.razor.css

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,56 @@
1+
/* @Media screen ... clause is copied from https://dev.to/turinumugisha_s/the-most-awesome-trick-for-tables-mobile-responsiveness-you-will-ever-need-32cp */
2+
3+
@media screen and (max-width: 600px) {
4+
table {
5+
border: 0;
6+
}
7+
8+
table caption {
9+
font-size: 1.3em;
10+
}
11+
12+
table thead {
13+
border: none;
14+
clip: rect(0 0 0 0);
15+
height: 1px;
16+
margin: -1px;
17+
overflow: hidden;
18+
padding: 0;
19+
position: absolute;
20+
width: 1px;
21+
}
22+
23+
table tr {
24+
border-bottom: 3px solid #ddd;
25+
display: block;
26+
/*box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.25);*/
27+
margin-bottom: .625em;
28+
}
29+
30+
/*Here we are setting each td display to block*/
31+
table td {
32+
border-bottom: 1px solid #ddd;
33+
display: block;
34+
font-size: .8em;
35+
text-align: right;
36+
}
37+
38+
/*
39+
Now the other data-label we have added on each td comes into play.
40+
*/
41+
42+
table td::before {
43+
content: attr(data-label);
44+
float: left;
45+
font-weight: bold;
46+
text-transform: uppercase;
47+
}
48+
49+
table td:last-child {
50+
border-bottom: 0;
51+
}
52+
}
53+
154
.title {
255
letter-spacing: 0.2rem;
356
font-variant: all-small-caps;

0 commit comments

Comments
 (0)