Client - customize chart tooltip (wip)
This commit is contained in:
parent
4e54d66c55
commit
00a400bc5b
@ -9,7 +9,8 @@
|
|||||||
import { ComputedRef, PropType, computed, defineComponent } from 'vue'
|
import { ComputedRef, PropType, computed, defineComponent } from 'vue'
|
||||||
import { BarChart, useBarChart } from 'vue-chart-3'
|
import { BarChart, useBarChart } from 'vue-chart-3'
|
||||||
|
|
||||||
import { IStatisticsChartDataset } from '@/types/statistics'
|
import { IStatisticsChartDataset, TDatasetKeys } from '@/types/statistics'
|
||||||
|
import { formatTooltipValue } from '@/utils/tooltip'
|
||||||
|
|
||||||
Chart.register(...registerables)
|
Chart.register(...registerables)
|
||||||
|
|
||||||
@ -27,6 +28,10 @@
|
|||||||
type: Object as PropType<unknown[]>,
|
type: Object as PropType<unknown[]>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
displayedData: {
|
||||||
|
type: String as PropType<TDatasetKeys>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
let chartData: ComputedRef<ChartData<'bar'>> = computed(() => ({
|
let chartData: ComputedRef<ChartData<'bar'>> = computed(() => ({
|
||||||
@ -57,6 +62,37 @@
|
|||||||
legend: {
|
legend: {
|
||||||
display: false,
|
display: false,
|
||||||
},
|
},
|
||||||
|
tooltip: {
|
||||||
|
interaction: {
|
||||||
|
intersect: true,
|
||||||
|
mode: 'index',
|
||||||
|
},
|
||||||
|
filter: function (tooltipItem) {
|
||||||
|
return tooltipItem.formattedValue !== '0'
|
||||||
|
},
|
||||||
|
callbacks: {
|
||||||
|
label: function (context) {
|
||||||
|
let label = context.dataset.label || ''
|
||||||
|
if (label) {
|
||||||
|
label += ': '
|
||||||
|
}
|
||||||
|
if (context.parsed.y !== null) {
|
||||||
|
label += formatTooltipValue(
|
||||||
|
props.displayedData,
|
||||||
|
context.parsed.y
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return label
|
||||||
|
},
|
||||||
|
footer: function (tooltipItems) {
|
||||||
|
let sum = 0
|
||||||
|
tooltipItems.map((tooltipItem) => {
|
||||||
|
sum += tooltipItem.parsed.y
|
||||||
|
})
|
||||||
|
return 'Total: ' + formatTooltipValue(props.displayedData, sum)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
const { barChartProps } = useBarChart({
|
const { barChartProps } = useBarChart({
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="stat-chart">
|
<div class="stat-chart">
|
||||||
<Chart :datasets="datasets" :labels="labels" />
|
<Chart
|
||||||
|
:datasets="datasets"
|
||||||
|
:labels="labels"
|
||||||
|
:displayedData="displayedData"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
20
fittrackee_client/src/utils/duration.ts
Normal file
20
fittrackee_client/src/utils/duration.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
export const formatDuration = (
|
||||||
|
totalSeconds: number,
|
||||||
|
formatWithUnits = false
|
||||||
|
): string => {
|
||||||
|
let days = '0'
|
||||||
|
if (formatWithUnits) {
|
||||||
|
days = String(Math.floor(totalSeconds / 86400))
|
||||||
|
totalSeconds %= 86400
|
||||||
|
}
|
||||||
|
const hours = String(Math.floor(totalSeconds / 3600)).padStart(2, '0')
|
||||||
|
totalSeconds %= 3600
|
||||||
|
const minutes = String(Math.floor(totalSeconds / 60)).padStart(2, '0')
|
||||||
|
const seconds = String(totalSeconds % 60).padStart(2, '0')
|
||||||
|
if (formatWithUnits) {
|
||||||
|
return `${days === '0' ? '' : `${days}d `}${
|
||||||
|
hours === '00' ? '' : `${hours}h `
|
||||||
|
}${minutes}m ${seconds}s`
|
||||||
|
}
|
||||||
|
return `${hours === '00' ? '' : `${hours}:`}${minutes}:${seconds}`
|
||||||
|
}
|
@ -19,7 +19,11 @@ const dateFormats: genericObject = {
|
|||||||
year: 'yyyy',
|
year: 'yyyy',
|
||||||
}
|
}
|
||||||
|
|
||||||
const data: TDatasetKeys[] = ['nb_workouts', 'total_duration', 'total_distance']
|
export const datasetKeys: TDatasetKeys[] = [
|
||||||
|
'nb_workouts',
|
||||||
|
'total_duration',
|
||||||
|
'total_distance',
|
||||||
|
]
|
||||||
|
|
||||||
export const getDateKeys = (
|
export const getDateKeys = (
|
||||||
params: IStatisticsDateParams,
|
params: IStatisticsDateParams,
|
||||||
@ -95,7 +99,7 @@ export const formatStats = (
|
|||||||
dayKeys.map((key) => {
|
dayKeys.map((key) => {
|
||||||
const date: string = format(key, dateFormat)
|
const date: string = format(key, dateFormat)
|
||||||
labels.push(date)
|
labels.push(date)
|
||||||
data.map((datasetKey) => {
|
datasetKeys.map((datasetKey) => {
|
||||||
datasets[datasetKey].map((dataset) => {
|
datasets[datasetKey].map((dataset) => {
|
||||||
dataset.data.push(
|
dataset.data.push(
|
||||||
apiStats !== {} &&
|
apiStats !== {} &&
|
||||||
|
13
fittrackee_client/src/utils/tooltip.ts
Normal file
13
fittrackee_client/src/utils/tooltip.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { TDatasetKeys } from '@/types/statistics'
|
||||||
|
import { formatDuration } from '@/utils/duration'
|
||||||
|
|
||||||
|
export const formatTooltipValue = (
|
||||||
|
displayedData: TDatasetKeys,
|
||||||
|
value: number
|
||||||
|
): string => {
|
||||||
|
return displayedData === 'total_duration'
|
||||||
|
? formatDuration(value, true)
|
||||||
|
: displayedData === 'total_distance'
|
||||||
|
? value.toFixed(2) + ' km'
|
||||||
|
: value.toString()
|
||||||
|
}
|
101
fittrackee_client/tests/unit/utils/duration.spec.ts
Normal file
101
fittrackee_client/tests/unit/utils/duration.spec.ts
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import { assert } from 'chai'
|
||||||
|
|
||||||
|
import { formatDuration } from '@/utils/duration'
|
||||||
|
|
||||||
|
describe('formatDuration (without days)', () => {
|
||||||
|
const testsParams = [
|
||||||
|
{
|
||||||
|
description: 'returns 00:00 if 0 seconds are provided',
|
||||||
|
inputDuration: 0,
|
||||||
|
expectedDuration: '00:00',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'returns 00:01 if 1 second is provided',
|
||||||
|
inputDuration: 1,
|
||||||
|
expectedDuration: '00:01',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'returns 01:00 if 60 seconds are provided',
|
||||||
|
inputDuration: 60,
|
||||||
|
expectedDuration: '01:00',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'returns 20:34 if 1234 seconds are provided',
|
||||||
|
inputDuration: 1234,
|
||||||
|
expectedDuration: '20:34',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'returns 01:00:00 if 3600 seconds are provided',
|
||||||
|
inputDuration: 3600,
|
||||||
|
expectedDuration: '01:00:00',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'returns 02:42:45 if 9765 seconds are provided',
|
||||||
|
inputDuration: 9765,
|
||||||
|
expectedDuration: '02:42:45',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'returns 02:42:45 if 9765 seconds are provided',
|
||||||
|
inputDuration: 97650,
|
||||||
|
expectedDuration: '27:07:30',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
testsParams.map((testParams) => {
|
||||||
|
it(testParams.description, () => {
|
||||||
|
assert.equal(
|
||||||
|
formatDuration(testParams.inputDuration),
|
||||||
|
testParams.expectedDuration
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('formatDuration (with days)', () => {
|
||||||
|
const testsParams = [
|
||||||
|
{
|
||||||
|
description: 'returns 00m 00s if 0 seconds are provided',
|
||||||
|
inputDuration: 0,
|
||||||
|
expectedDuration: '00m 00s',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'returns 00m 01s if 1 second is provided',
|
||||||
|
inputDuration: 1,
|
||||||
|
expectedDuration: '00m 01s',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'returns 01m 00s if 60 seconds are provided',
|
||||||
|
inputDuration: 60,
|
||||||
|
expectedDuration: '01m 00s',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'returns 20m 34s if 1234 seconds are provided',
|
||||||
|
inputDuration: 1234,
|
||||||
|
expectedDuration: '20m 34s',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'returns 01h 00m 00s if 3600 seconds are provided',
|
||||||
|
inputDuration: 3600,
|
||||||
|
expectedDuration: '01h 00m 00s',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'returns 02h 42m 45s if 9765 seconds are provided',
|
||||||
|
inputDuration: 9765,
|
||||||
|
expectedDuration: '02h 42m 45s',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'returns 1d 03h 07m 30s if 9765 seconds are provided',
|
||||||
|
inputDuration: 97650,
|
||||||
|
expectedDuration: '1d 03h 07m 30s',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
testsParams.map((testParams) => {
|
||||||
|
it(testParams.description, () => {
|
||||||
|
assert.equal(
|
||||||
|
formatDuration(testParams.inputDuration, true),
|
||||||
|
testParams.expectedDuration
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
39
fittrackee_client/tests/unit/utils/tooltip.spec.ts
Normal file
39
fittrackee_client/tests/unit/utils/tooltip.spec.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { assert } from 'chai'
|
||||||
|
|
||||||
|
import { datasetKeys } from '@/utils/statistics'
|
||||||
|
import { formatTooltipValue } from '@/utils/tooltip'
|
||||||
|
|
||||||
|
describe('formatTooltipValue', () => {
|
||||||
|
const testsParams = [
|
||||||
|
{
|
||||||
|
description: 'returns 3 if input is workouts count',
|
||||||
|
inputDisplayedData: datasetKeys[0], // 'nb_workouts'
|
||||||
|
inputValue: 3,
|
||||||
|
expectedResult: '3',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'returns 00m:03s if input is total duration',
|
||||||
|
inputDisplayedData: datasetKeys[1], // 'total_duration'
|
||||||
|
inputValue: 3,
|
||||||
|
expectedResult: '00m 03s',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'returns 3.00 if input is total distance',
|
||||||
|
inputDisplayedData: datasetKeys[2], // 'total_distance'
|
||||||
|
inputValue: 3,
|
||||||
|
expectedResult: '3.00 km',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
testsParams.map((testParams) => {
|
||||||
|
it(testParams.description, () => {
|
||||||
|
assert.equal(
|
||||||
|
formatTooltipValue(
|
||||||
|
testParams.inputDisplayedData,
|
||||||
|
testParams.inputValue
|
||||||
|
),
|
||||||
|
testParams.expectedResult
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user