Client - customize chart tooltip (wip)

This commit is contained in:
Sam 2021-09-04 21:36:59 +02:00
parent 4e54d66c55
commit 00a400bc5b
7 changed files with 221 additions and 4 deletions

View File

@ -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({

View File

@ -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>

View 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}`
}

View File

@ -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 !== {} &&

View 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()
}

View 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
)
})
})
})

View 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
)
})
})
})