Client - display a marker on map when mouse is over the chart
This commit is contained in:
parent
2736368626
commit
279271af42
@ -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,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -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()
|
||||||
|
@ -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[]>,
|
||||||
},
|
},
|
||||||
|
@ -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[]
|
||||||
}
|
}
|
||||||
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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 },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user