Client - display a marker on map when mouse is over the chart

This commit is contained in:
Sam 2021-09-26 11:21:09 +02:00
parent 2736368626
commit 279271af42
7 changed files with 77 additions and 11 deletions

View File

@ -23,7 +23,11 @@
{{ t('workouts.DURATION') }} {{ t('workouts.DURATION') }}
</label> </label>
</div> </div>
<LineChart v-bind="lineChartProps" class="line-chart" /> <LineChart
v-bind="lineChartProps"
class="line-chart"
@mouseleave="emitEmptyCoordinates"
/>
<div class="no-data-cleaning"> <div class="no-data-cleaning">
{{ t('workouts.NO_DATA_CLEANING') }} {{ t('workouts.NO_DATA_CLEANING') }}
</div> </div>
@ -40,7 +44,11 @@
import Card from '@/components/Common/Card.vue' import Card from '@/components/Common/Card.vue'
import { IAuthUserProfile } from '@/types/user' import { IAuthUserProfile } from '@/types/user'
import { IWorkoutChartData, IWorkoutState } from '@/types/workouts' import {
IWorkoutChartData,
IWorkoutState,
TCoordinates,
} from '@/types/workouts'
import { getDatasets } from '@/utils/workouts' import { getDatasets } from '@/utils/workouts'
export default defineComponent({ export default defineComponent({
@ -59,7 +67,8 @@
required: true, required: true,
}, },
}, },
setup(props) { emits: ['getCoordinates'],
setup(props, { emit }) {
const { t } = useI18n() const { t } = useI18n()
let displayDistance = ref(true) let displayDistance = ref(true)
const datasets: ComputedRef<IWorkoutChartData> = computed(() => const datasets: ComputedRef<IWorkoutChartData> = computed(() =>
@ -76,6 +85,9 @@
]) ])
), ),
})) }))
const coordinates: ComputedRef<TCoordinates[]> = computed(
() => datasets.value.coordinates
)
const options = computed<ChartOptions<'line'>>(() => ({ const options = computed<ChartOptions<'line'>>(() => ({
responsive: true, responsive: true,
animation: false, animation: false,
@ -151,6 +163,9 @@
: label + ' km/h' : label + ' km/h'
}, },
title: function (tooltipItems) { title: function (tooltipItems) {
if (tooltipItems.length > 0) {
emitCoordinates(coordinates.value[tooltipItems[0].dataIndex])
}
return tooltipItems.length === 0 return tooltipItems.length === 0
? '' ? ''
: displayDistance.value : displayDistance.value
@ -168,6 +183,12 @@
function formatDuration(duration: string | number): string { function formatDuration(duration: string | number): string {
return new Date(+duration * 1000).toISOString().substr(11, 8) return new Date(+duration * 1000).toISOString().substr(11, 8)
} }
function emitCoordinates(coordinates: TCoordinates) {
emit('getCoordinates', coordinates)
}
function emitEmptyCoordinates() {
emitCoordinates({ latitude: null, longitude: null })
}
const { lineChartProps } = useLineChart({ const { lineChartProps } = useLineChart({
chartData, chartData,
@ -177,6 +198,7 @@
displayDistance, displayDistance,
lineChartProps, lineChartProps,
t, t,
emitEmptyCoordinates,
updateDisplayDistance, updateDisplayDistance,
} }
}, },

View File

@ -17,6 +17,10 @@
:bounds="bounds" :bounds="bounds"
/> />
<LGeoJson :geojson="geoJson.jsonData" /> <LGeoJson :geojson="geoJson.jsonData" />
<LMarker
v-if="markerCoordinates.latitude"
:lat-lng="[markerCoordinates.latitude, markerCoordinates.longitude]"
/>
</LMap> </LMap>
</div> </div>
<div v-else class="no-map">{{ t('workouts.NO_MAP') }}</div> <div v-else class="no-map">{{ t('workouts.NO_MAP') }}</div>
@ -25,13 +29,13 @@
<script lang="ts"> <script lang="ts">
import { gpx } from '@tmcw/togeojson' import { gpx } from '@tmcw/togeojson'
import { LGeoJson, LMap, LTileLayer } from '@vue-leaflet/vue-leaflet' import { LGeoJson, LMap, LMarker, LTileLayer } from '@vue-leaflet/vue-leaflet'
import { ComputedRef, PropType, computed, defineComponent, ref } from 'vue' import { ComputedRef, PropType, computed, defineComponent, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { ROOT_STORE } from '@/store/constants' import { ROOT_STORE } from '@/store/constants'
import { GeoJSONData } from '@/types/geojson' import { GeoJSONData } from '@/types/geojson'
import { IWorkoutState } from '@/types/workouts' import { IWorkoutState, TCoordinates } from '@/types/workouts'
import { useStore } from '@/use/useStore' import { useStore } from '@/use/useStore'
import { getApiUrl } from '@/utils' import { getApiUrl } from '@/utils'
@ -40,12 +44,17 @@
components: { components: {
LGeoJson, LGeoJson,
LMap, LMap,
LMarker,
LTileLayer, LTileLayer,
}, },
props: { props: {
workout: { workout: {
type: Object as PropType<IWorkoutState>, type: Object as PropType<IWorkoutState>,
}, },
markerCoordinates: {
type: Object as PropType<TCoordinates>,
required: false,
},
}, },
setup(props) { setup(props) {
const { t } = useI18n() const { t } = useI18n()

View File

@ -53,7 +53,7 @@
</div> </div>
</template> </template>
<template #content> <template #content>
<WorkoutMap :workout="workout" /> <WorkoutMap :workout="workout" :markerCoordinates="markerCoordinates" />
<WorkoutData :workout="workout.workout" /> <WorkoutData :workout="workout.workout" />
</template> </template>
</Card> </Card>
@ -71,7 +71,7 @@
import { WORKOUTS_STORE } from '@/store/constants' import { WORKOUTS_STORE } from '@/store/constants'
import { ISport } from '@/types/sports' import { ISport } from '@/types/sports'
import { IAuthUserProfile } from '@/types/user' import { IAuthUserProfile } from '@/types/user'
import { IWorkoutState } from '@/types/workouts' import { IWorkoutState, TCoordinates } from '@/types/workouts'
import { useStore } from '@/use/useStore' import { useStore } from '@/use/useStore'
import { formatWorkoutDate, getDateWithTZ } from '@/utils/dates' import { formatWorkoutDate, getDateWithTZ } from '@/utils/dates'
@ -87,6 +87,10 @@
type: Object as PropType<IAuthUserProfile>, type: Object as PropType<IAuthUserProfile>,
required: true, required: true,
}, },
markerCoordinates: {
type: Object as PropType<TCoordinates>,
required: false,
},
sports: { sports: {
type: Object as PropType<ISport[]>, type: Object as PropType<ISport[]>,
}, },

View File

@ -104,8 +104,15 @@ export type TWorkoutDatasets = {
[key in TWorkoutDatasetKeys]: IChartDataset [key in TWorkoutDatasetKeys]: IChartDataset
} }
export type TCoordinatesKeys = 'latitude' | 'longitude'
export type TCoordinates = {
[key in TCoordinatesKeys]: number | null
}
export interface IWorkoutChartData { export interface IWorkoutChartData {
distance_labels: unknown[] distance_labels: unknown[]
duration_labels: unknown[] duration_labels: unknown[]
datasets: TWorkoutDatasets datasets: TWorkoutDatasets
coordinates: TCoordinates[]
} }

View File

@ -1,6 +1,7 @@
import { import {
IWorkoutApiChartData, IWorkoutApiChartData,
IWorkoutChartData, IWorkoutChartData,
TCoordinates,
TWorkoutDatasets, TWorkoutDatasets,
} from '@/types/workouts' } from '@/types/workouts'
@ -29,13 +30,15 @@ export const getDatasets = (
} }
const distance_labels: unknown[] = [] const distance_labels: unknown[] = []
const duration_labels: unknown[] = [] const duration_labels: unknown[] = []
const coordinates: TCoordinates[] = []
chartData.map((data) => { chartData.map((data) => {
distance_labels.push(data.distance) distance_labels.push(data.distance)
duration_labels.push(data.duration) duration_labels.push(data.duration)
datasets.speed.data.push(data.speed) datasets.speed.data.push(data.speed)
datasets.elevation.data.push(data.elevation) datasets.elevation.data.push(data.elevation)
coordinates.push({ latitude: data.latitude, longitude: data.longitude })
}) })
return { distance_labels, duration_labels, datasets } return { distance_labels, duration_labels, datasets, coordinates }
} }

View File

@ -13,11 +13,13 @@
:workout="workout" :workout="workout"
:sports="sports" :sports="sports"
:authUser="authUser" :authUser="authUser"
:markerCoordinates="markerCoordinates"
/> />
<WorkoutChart <WorkoutChart
v-if="workout.chartData.length > 0" v-if="workout.chartData.length > 0"
:workout="workout" :workout="workout"
:authUser="authUser" :authUser="authUser"
@getCoordinates="updateCoordinates"
/> />
</div> </div>
<div v-else> <div v-else>
@ -30,9 +32,11 @@
<script lang="ts"> <script lang="ts">
import { import {
computed,
ComputedRef, ComputedRef,
Ref,
computed,
defineComponent, defineComponent,
ref,
onBeforeMount, onBeforeMount,
onUnmounted, onUnmounted,
} from 'vue' } from 'vue'
@ -44,7 +48,7 @@
import WorkoutDetail from '@/components/Workout/WorkoutDetail/index.vue' import WorkoutDetail from '@/components/Workout/WorkoutDetail/index.vue'
import { SPORTS_STORE, USER_STORE, WORKOUTS_STORE } from '@/store/constants' import { SPORTS_STORE, USER_STORE, WORKOUTS_STORE } from '@/store/constants'
import { IAuthUserProfile } from '@/types/user' import { IAuthUserProfile } from '@/types/user'
import { IWorkoutState } from '@/types/workouts' import { IWorkoutState, TCoordinates } from '@/types/workouts'
import { useStore } from '@/use/useStore' import { useStore } from '@/use/useStore'
export default defineComponent({ export default defineComponent({
@ -72,11 +76,22 @@
() => store.getters[USER_STORE.GETTERS.AUTH_USER_PROFILE] () => store.getters[USER_STORE.GETTERS.AUTH_USER_PROFILE]
) )
const sports = computed(() => store.getters[SPORTS_STORE.GETTERS.SPORTS]) const sports = computed(() => store.getters[SPORTS_STORE.GETTERS.SPORTS])
let markerCoordinates: Ref<TCoordinates> = ref({
latitude: null,
longitude: null,
})
function updateCoordinates(coordinates: TCoordinates) {
markerCoordinates.value = {
latitude: coordinates.latitude,
longitude: coordinates.longitude,
}
}
onUnmounted(() => { onUnmounted(() => {
store.commit(WORKOUTS_STORE.MUTATIONS.EMPTY_WORKOUT) store.commit(WORKOUTS_STORE.MUTATIONS.EMPTY_WORKOUT)
}) })
return { authUser, sports, workout } return { authUser, markerCoordinates, sports, workout, updateCoordinates }
}, },
}) })
</script> </script>

View File

@ -36,6 +36,7 @@ describe('getDatasets', () => {
yAxisID: 'yElevation', yAxisID: 'yElevation',
}, },
}, },
coordinates: [],
}, },
}, },
{ {
@ -94,6 +95,11 @@ describe('getDatasets', () => {
yAxisID: 'yElevation', yAxisID: 'yElevation',
}, },
}, },
coordinates: [
{ latitude: 48.845574, longitude: 2.373723 },
{ latitude: 48.845578, longitude: 2.373732 },
{ latitude: 48.845591, longitude: 2.373811 },
],
}, },
}, },
] ]