Client - init workout chart (WIP)
This commit is contained in:
@ -5,25 +5,15 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
Chart,
|
||||
ChartData,
|
||||
ChartOptions,
|
||||
LayoutItem,
|
||||
registerables,
|
||||
} from 'chart.js'
|
||||
import ChartDataLabels from 'chartjs-plugin-datalabels'
|
||||
import { ChartData, ChartOptions, LayoutItem } from 'chart.js'
|
||||
import { ComputedRef, PropType, computed, defineComponent } from 'vue'
|
||||
import { BarChart, useBarChart } from 'vue-chart-3'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { IChartDataset } from '@/types/chart'
|
||||
import { TDatasetKeys } from '@/types/statistics'
|
||||
import { TStatisticsDatasetKeys } from '@/types/statistics'
|
||||
import { formatTooltipValue } from '@/utils/tooltip'
|
||||
|
||||
Chart.register(...registerables)
|
||||
Chart.register(ChartDataLabels)
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Chart',
|
||||
components: {
|
||||
@ -39,7 +29,7 @@
|
||||
required: true,
|
||||
},
|
||||
displayedData: {
|
||||
type: String as PropType<TDatasetKeys>,
|
||||
type: String as PropType<TStatisticsDatasetKeys>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
@ -16,7 +16,7 @@
|
||||
import {
|
||||
IStatisticsChartData,
|
||||
IStatisticsDateParams,
|
||||
TDatasetKeys,
|
||||
TStatisticsDatasetKeys,
|
||||
TStatisticsFromApi,
|
||||
} from '@/types/statistics'
|
||||
import { formatStats } from '@/utils/statistics'
|
||||
@ -32,7 +32,7 @@
|
||||
required: true,
|
||||
},
|
||||
displayedData: {
|
||||
type: String as PropType<TDatasetKeys>,
|
||||
type: String as PropType<TStatisticsDatasetKeys>,
|
||||
required: true,
|
||||
},
|
||||
params: {
|
||||
|
188
fittrackee_client/src/components/Workout/WorkoutChart/index.vue
Normal file
188
fittrackee_client/src/components/Workout/WorkoutChart/index.vue
Normal file
@ -0,0 +1,188 @@
|
||||
<template>
|
||||
<div id="workout-chart">
|
||||
<Card :without-title="false">
|
||||
<template #title>{{ t('workouts.ANALYSIS') }} </template>
|
||||
<template #content>
|
||||
<div class="chart-radio">
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="distance"
|
||||
:checked="displayDistance"
|
||||
@click="updateDisplayDistance"
|
||||
/>
|
||||
{{ t('workouts.DISTANCE') }}
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
name="duration"
|
||||
:checked="!displayDistance"
|
||||
@click="updateDisplayDistance"
|
||||
/>
|
||||
{{ t('workouts.DURATION') }}
|
||||
</label>
|
||||
</div>
|
||||
<LineChart v-bind="lineChartProps" class="line-chart" />
|
||||
<div class="no-data-cleaning">
|
||||
{{ t('workouts.NO_DATA_CLEANING') }}
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ChartData, ChartOptions } from 'chart.js'
|
||||
import { PropType, defineComponent, ref, ComputedRef, computed } from 'vue'
|
||||
import { LineChart, useLineChart } from 'vue-chart-3'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import Card from '@/components/Common/Card.vue'
|
||||
import { IAuthUserProfile } from '@/types/user'
|
||||
import { IWorkoutChartData, IWorkoutState } from '@/types/workouts'
|
||||
import { getDatasets } from '@/utils/workouts'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'WorkoutChart',
|
||||
components: {
|
||||
Card,
|
||||
LineChart,
|
||||
},
|
||||
props: {
|
||||
authUser: {
|
||||
type: Object as PropType<IAuthUserProfile>,
|
||||
required: true,
|
||||
},
|
||||
workout: {
|
||||
type: Object as PropType<IWorkoutState>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { t } = useI18n()
|
||||
let displayDistance = ref(true)
|
||||
const datasets: ComputedRef<IWorkoutChartData> = computed(() =>
|
||||
getDatasets(props.workout.chartData, t)
|
||||
)
|
||||
let chartData: ComputedRef<ChartData<'line'>> = computed(() => ({
|
||||
labels: displayDistance.value
|
||||
? datasets.value.distance_labels
|
||||
: datasets.value.duration_labels,
|
||||
datasets: JSON.parse(
|
||||
JSON.stringify([
|
||||
datasets.value.datasets.speed,
|
||||
datasets.value.datasets.elevation,
|
||||
])
|
||||
),
|
||||
}))
|
||||
const options = computed<ChartOptions<'line'>>(() => ({
|
||||
responsive: true,
|
||||
animation: false,
|
||||
layout: {
|
||||
padding: {
|
||||
top: 22,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
ticks: {
|
||||
count: 10,
|
||||
callback: function (value) {
|
||||
return displayDistance.value
|
||||
? Number(value).toFixed(2)
|
||||
: new Date(+value * 1000).toISOString().substr(11, 8)
|
||||
},
|
||||
},
|
||||
type: 'linear',
|
||||
bounds: 'data',
|
||||
title: {
|
||||
display: true,
|
||||
text: displayDistance.value
|
||||
? t('workouts.DISTANCE') + ' (km)'
|
||||
: t('workouts.DURATION'),
|
||||
},
|
||||
},
|
||||
ySpeed: {
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
position: 'left',
|
||||
title: {
|
||||
display: true,
|
||||
text: t('workouts.SPEED') + ' (km/h)',
|
||||
},
|
||||
},
|
||||
yElevation: {
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
position: 'right',
|
||||
title: {
|
||||
display: true,
|
||||
text: t('workouts.ELEVATION') + ' (m)',
|
||||
},
|
||||
},
|
||||
},
|
||||
elements: {
|
||||
point: {
|
||||
pointStyle: 'circle',
|
||||
pointRadius: 0,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
datalabels: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
function updateDisplayDistance() {
|
||||
displayDistance.value = !displayDistance.value
|
||||
}
|
||||
|
||||
const { lineChartProps } = useLineChart({
|
||||
chartData,
|
||||
options,
|
||||
})
|
||||
return {
|
||||
displayDistance,
|
||||
lineChartProps,
|
||||
t,
|
||||
updateDisplayDistance,
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/scss/base';
|
||||
#workout-chart {
|
||||
::v-deep(.card) {
|
||||
.card-title {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.card-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.chart-radio {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
label {
|
||||
padding: 0 $default-padding;
|
||||
}
|
||||
}
|
||||
.no-data-cleaning {
|
||||
font-size: 0.85em;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user