mirror of
https://github.com/joaovitoriasilva/endurain.git
synced 2026-01-09 15:57:59 -05:00
Merge branch 'pr/442' into pre-release
This commit is contained in:
@@ -369,7 +369,7 @@ def transform_activity_streams_hr(activity_stream, activity, db):
|
|||||||
activity: The activity object associated with the stream, used to retrieve the user ID.
|
activity: The activity object associated with the stream, used to retrieve the user ID.
|
||||||
db: The database session or connection used to fetch user details.
|
db: The database session or connection used to fetch user details.
|
||||||
Returns:
|
Returns:
|
||||||
The activity stream object with an added 'hr_zone_percentages' attribute, which contains the percentage of time spent in each heart rate zone and their respective HR boundaries. If waypoints or user details are missing, returns the original activity stream unchanged.
|
The activity stream object with an added 'hr_zone_percentages' attribute, which contains the percentage of time spent in each heart rate zone and their respective HR boundaries. If waypoi[...]
|
||||||
Notes:
|
Notes:
|
||||||
- Heart rate zones are calculated using the formula: max_heart_rate = 220 - age.
|
- Heart rate zones are calculated using the formula: max_heart_rate = 220 - age.
|
||||||
- The function expects waypoints to be a list of dicts with an "hr" key.
|
- The function expects waypoints to be a list of dicts with an "hr" key.
|
||||||
@@ -423,7 +423,25 @@ def transform_activity_streams_hr(activity_stream, activity, db):
|
|||||||
np.sum((hr_values >= zone_3) & (hr_values < zone_4)),
|
np.sum((hr_values >= zone_3) & (hr_values < zone_4)),
|
||||||
np.sum(hr_values >= zone_4),
|
np.sum(hr_values >= zone_4),
|
||||||
]
|
]
|
||||||
zone_percentages = [round((count / total) * 100, 2) for count in zone_counts]
|
zone_percentages = [
|
||||||
|
round((count / total) * 100, 2) for count in zone_counts
|
||||||
|
]
|
||||||
|
|
||||||
|
# Calculate time in seconds for each zone using the percentage
|
||||||
|
# of total_timer_time
|
||||||
|
has_timer_time = (
|
||||||
|
hasattr(activity, "total_timer_time")
|
||||||
|
and activity.total_timer_time
|
||||||
|
)
|
||||||
|
if has_timer_time:
|
||||||
|
total_time_seconds = activity.total_timer_time
|
||||||
|
zone_time_seconds = [
|
||||||
|
int((percent / 100) * total_time_seconds)
|
||||||
|
for percent in zone_percentages
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
# Fallback: no time calculation possible
|
||||||
|
zone_time_seconds = [0, 0, 0, 0, 0]
|
||||||
|
|
||||||
# Calculate zone HR boundaries for display
|
# Calculate zone HR boundaries for display
|
||||||
zone_hr = {
|
zone_hr = {
|
||||||
@@ -434,11 +452,31 @@ def transform_activity_streams_hr(activity_stream, activity, db):
|
|||||||
"zone_5": f">= {int(zone_4)}",
|
"zone_5": f">= {int(zone_4)}",
|
||||||
}
|
}
|
||||||
activity_stream.hr_zone_percentages = {
|
activity_stream.hr_zone_percentages = {
|
||||||
"zone_1": {"percent": zone_percentages[0], "hr": zone_hr["zone_1"]},
|
"zone_1": {
|
||||||
"zone_2": {"percent": zone_percentages[1], "hr": zone_hr["zone_2"]},
|
"percent": zone_percentages[0],
|
||||||
"zone_3": {"percent": zone_percentages[2], "hr": zone_hr["zone_3"]},
|
"hr": zone_hr["zone_1"],
|
||||||
"zone_4": {"percent": zone_percentages[3], "hr": zone_hr["zone_4"]},
|
"time_seconds": zone_time_seconds[0],
|
||||||
"zone_5": {"percent": zone_percentages[4], "hr": zone_hr["zone_5"]},
|
},
|
||||||
|
"zone_2": {
|
||||||
|
"percent": zone_percentages[1],
|
||||||
|
"hr": zone_hr["zone_2"],
|
||||||
|
"time_seconds": zone_time_seconds[1],
|
||||||
|
},
|
||||||
|
"zone_3": {
|
||||||
|
"percent": zone_percentages[2],
|
||||||
|
"hr": zone_hr["zone_3"],
|
||||||
|
"time_seconds": zone_time_seconds[2],
|
||||||
|
},
|
||||||
|
"zone_4": {
|
||||||
|
"percent": zone_percentages[3],
|
||||||
|
"hr": zone_hr["zone_4"],
|
||||||
|
"time_seconds": zone_time_seconds[3],
|
||||||
|
},
|
||||||
|
"zone_5": {
|
||||||
|
"percent": zone_percentages[4],
|
||||||
|
"hr": zone_hr["zone_5"],
|
||||||
|
"time_seconds": zone_time_seconds[4],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return activity_stream
|
return activity_stream
|
||||||
|
|||||||
@@ -138,10 +138,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<BarChartComponent
|
<BarChartComponent
|
||||||
v-if="Object.values(hrZones).length > 0 && hrPresent"
|
v-if="Object.values(hrZones).length > 0 && hrPresent"
|
||||||
:labels="getHrBarChartData(hrZones, t).labels"
|
:labels="hrChartData.labels"
|
||||||
:values="getHrBarChartData(hrZones, t).values"
|
:values="hrChartData.values"
|
||||||
:barColors="getHrBarChartData(hrZones, t).barColors"
|
:barColors="hrChartData.barColors"
|
||||||
:datalabelsFormatter="(value) => `${Math.round(value)}%`"
|
:timeSeconds="hrChartData.timeSeconds"
|
||||||
|
:datalabelsFormatter="
|
||||||
|
(value, context) => formatHrZoneLabel(value, hrChartData.timeSeconds[context.dataIndex])
|
||||||
|
"
|
||||||
:title="$t('activityMandAbovePillsComponent.labelHRZones')"
|
:title="$t('activityMandAbovePillsComponent.labelHRZones')"
|
||||||
/>
|
/>
|
||||||
<hr />
|
<hr />
|
||||||
@@ -346,6 +349,9 @@ const pacePresent = ref(false)
|
|||||||
const formattedPace = ref(null)
|
const formattedPace = ref(null)
|
||||||
const hrZones = ref({})
|
const hrZones = ref({})
|
||||||
|
|
||||||
|
// Computed properties
|
||||||
|
const hrChartData = computed(() => getHrBarChartData(hrZones.value, t))
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
if (props.activityActivityStreams && props.activityActivityStreams.length > 0) {
|
if (props.activityActivityStreams && props.activityActivityStreams.length > 0) {
|
||||||
|
|||||||
@@ -120,10 +120,13 @@
|
|||||||
/>
|
/>
|
||||||
<BarChartComponent
|
<BarChartComponent
|
||||||
v-if="Object.values(hrZones).length > 0 && graphSelection === 'hrZones' && hrPresent"
|
v-if="Object.values(hrZones).length > 0 && graphSelection === 'hrZones' && hrPresent"
|
||||||
:labels="getHrBarChartData(hrZones, t).labels"
|
:labels="hrChartData.labels"
|
||||||
:values="getHrBarChartData(hrZones, t).values"
|
:values="hrChartData.values"
|
||||||
:barColors="getHrBarChartData(hrZones, t).barColors"
|
:barColors="hrChartData.barColors"
|
||||||
:datalabelsFormatter="(value) => `${Math.round(value)}%`"
|
:timeSeconds="hrChartData.timeSeconds"
|
||||||
|
:datalabelsFormatter="
|
||||||
|
(value, context) => formatHrZoneLabel(value, hrChartData.timeSeconds[context.dataIndex])
|
||||||
|
"
|
||||||
:title="$t('activityMandAbovePillsComponent.labelHRZones')"
|
:title="$t('activityMandAbovePillsComponent.labelHRZones')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -167,7 +170,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
// Importing the components
|
// Importing the components
|
||||||
import ActivityLapsComponent from '@/components/Activities/ActivityLapsComponent.vue'
|
import ActivityLapsComponent from '@/components/Activities/ActivityLapsComponent.vue'
|
||||||
@@ -186,7 +189,7 @@ import {
|
|||||||
// Import Notivue push
|
// Import Notivue push
|
||||||
import { push } from 'notivue'
|
import { push } from 'notivue'
|
||||||
// Import the utils
|
// Import the utils
|
||||||
import { getHrBarChartData } from '@/utils/chartUtils'
|
import { getHrBarChartData, formatHrZoneLabel } from '@/utils/chartUtils'
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -234,6 +237,9 @@ const velPresent = ref(false)
|
|||||||
const pacePresent = ref(false)
|
const pacePresent = ref(false)
|
||||||
const hrZones = ref({})
|
const hrZones = ref({})
|
||||||
|
|
||||||
|
// Computed properties
|
||||||
|
const hrChartData = computed(() => getHrBarChartData(hrZones.value, t))
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
function selectGraph(type) {
|
function selectGraph(type) {
|
||||||
graphSelection.value = type
|
graphSelection.value = type
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ const props = defineProps({
|
|||||||
datalabelsFormatter: {
|
datalabelsFormatter: {
|
||||||
type: Function,
|
type: Function,
|
||||||
default: null
|
default: null
|
||||||
|
},
|
||||||
|
timeSeconds: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Formats seconds into a human-readable duration string.
|
||||||
|
* @param {number} seconds - The total number of seconds to format.
|
||||||
|
* @returns {string} Formatted duration string (e.g., "2h 30m" or "45m").
|
||||||
|
*/
|
||||||
|
export function formatDuration(seconds) {
|
||||||
|
const hours = Math.floor(seconds / 3600)
|
||||||
|
const minutes = Math.floor((seconds % 3600) / 60)
|
||||||
|
if (hours > 0) {
|
||||||
|
return `${hours}h ${minutes}m`
|
||||||
|
}
|
||||||
|
return `${minutes}m`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats HR zone label with percentage and optional time duration.
|
||||||
|
* @param {number} value - Percentage value
|
||||||
|
* @param {number|null} timeSeconds - Time in seconds (0 or null means no time)
|
||||||
|
* @returns {string} Formatted label (e.g., "25%" or "25% (15m)")
|
||||||
|
*/
|
||||||
|
export function formatHrZoneLabel(value, timeSeconds) {
|
||||||
|
const percentage = `${Math.round(value)}%`
|
||||||
|
if (!timeSeconds || timeSeconds === 0) {
|
||||||
|
return percentage
|
||||||
|
}
|
||||||
|
const timeStr = formatDuration(timeSeconds)
|
||||||
|
return `${percentage} (${timeStr})`
|
||||||
|
}
|
||||||
|
|
||||||
export function getZoneColor(index) {
|
export function getZoneColor(index) {
|
||||||
// Example colors for 5 HR zones
|
// Example colors for 5 HR zones
|
||||||
const colors = [
|
const colors = [
|
||||||
@@ -18,6 +47,7 @@ export function getHrBarChartData(hrZones, t) {
|
|||||||
),
|
),
|
||||||
// values: zones.map(z => `${z.percent ?? 0}%`),
|
// values: zones.map(z => `${z.percent ?? 0}%`),
|
||||||
values: zones.map((z) => z.percent ?? 0),
|
values: zones.map((z) => z.percent ?? 0),
|
||||||
barColors: zones.map((_, i) => getZoneColor(i))
|
barColors: zones.map((_, i) => getZoneColor(i)),
|
||||||
|
timeSeconds: zones.map((z) => z.time_seconds ?? 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user