Client: display elevation and speed chart for an activity
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
import mpwoGenericApi from '../mwpoApi'
|
||||
import mpwoApi from '../mwpoApi/activities'
|
||||
import { history } from '../index'
|
||||
import { formatChartData } from '../utils'
|
||||
import { setError } from './index'
|
||||
|
||||
export const pushActivities = activities => ({
|
||||
@ -13,6 +14,11 @@ export const setGpx = gpxContent => ({
|
||||
gpxContent,
|
||||
})
|
||||
|
||||
export const setChartData = chartData => ({
|
||||
type: 'SET_CHART_DATA',
|
||||
chartData,
|
||||
})
|
||||
|
||||
export const addActivity = form => dispatch => mpwoApi
|
||||
.addActivity(form)
|
||||
.then(ret => {
|
||||
@ -54,6 +60,23 @@ export const getActivityGpx = activityId => dispatch => {
|
||||
}
|
||||
|
||||
|
||||
export const getActivityChartData = activityId => dispatch => {
|
||||
if (activityId) {
|
||||
return mpwoApi
|
||||
.getActivityChartData(activityId)
|
||||
.then(ret => {
|
||||
if (ret.status === 'success') {
|
||||
dispatch(setChartData(formatChartData(ret.data.chart_data)))
|
||||
} else {
|
||||
dispatch(setError(`activities: ${ret.message}`))
|
||||
}
|
||||
})
|
||||
.catch(error => dispatch(setError(`activities: ${error}`)))
|
||||
}
|
||||
dispatch(setChartData(null))
|
||||
}
|
||||
|
||||
|
||||
export const deleteActivity = id => dispatch => mpwoGenericApi
|
||||
.deleteData('activities', id)
|
||||
.then(ret => {
|
||||
|
@ -0,0 +1,84 @@
|
||||
import { format } from 'date-fns'
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import {
|
||||
Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis
|
||||
} from 'recharts'
|
||||
|
||||
import { getActivityChartData } from '../../../actions/activities'
|
||||
|
||||
|
||||
class ActivityCharts extends React.Component {
|
||||
componentDidMount() {
|
||||
this.props.loadActivityData(this.props.activity.id)
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.activity.id !==
|
||||
this.props.activity.id) {
|
||||
this.props.loadActivityData(this.props.activity.id)
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.loadActivityData(null)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { chartData } = this.props
|
||||
return (
|
||||
<div>
|
||||
<ResponsiveContainer height={300}>
|
||||
<LineChart
|
||||
data={chartData}
|
||||
margin={{ top: 15, right: 30, left: 20, bottom: 15 }}
|
||||
>
|
||||
<XAxis
|
||||
dataKey="duration"
|
||||
label={{ value: 'duration', offset: 0, position: 'bottom' }}
|
||||
scale="time"
|
||||
tickFormatter={time => format(time, 'HH:mm:ss')}
|
||||
type="number"
|
||||
/>
|
||||
<YAxis
|
||||
label={{ value: 'speed (km/h)', angle: -90, position: 'left' }}
|
||||
yAxisId="left"
|
||||
/>
|
||||
<YAxis
|
||||
label={{ value: 'altitude (m)', angle: -90, position: 'right' }}
|
||||
yAxisId="right" orientation="right"
|
||||
/>
|
||||
<Line
|
||||
yAxisId="left"
|
||||
type="linear"
|
||||
dataKey="speed"
|
||||
stroke="#8884d8"
|
||||
dot={false}
|
||||
/>
|
||||
<Line
|
||||
yAxisId="right"
|
||||
type="linear"
|
||||
dataKey="elevation"
|
||||
stroke="#808080"
|
||||
dot={false}
|
||||
/>
|
||||
<Tooltip
|
||||
labelFormatter={time => format(time, 'HH:mm:ss')}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
chartData: state.chartData
|
||||
}),
|
||||
dispatch => ({
|
||||
loadActivityData: activityId => {
|
||||
dispatch(getActivityChartData(activityId))
|
||||
},
|
||||
})
|
||||
)(ActivityCharts)
|
@ -3,6 +3,7 @@ import { Helmet } from 'react-helmet'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import ActivityCardHeader from './ActivityCardHeader'
|
||||
import ActivityCharts from './ActivityCharts'
|
||||
import ActivityDetails from './ActivityDetails'
|
||||
import ActivityMap from './ActivityMap'
|
||||
import CustomModal from './../../Others/CustomModal'
|
||||
@ -61,26 +62,41 @@ class ActivityDisplay extends React.Component {
|
||||
close={() => this.displayModal(false)}
|
||||
/>}
|
||||
{activity && sport && activities.length === 1 && (
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<ActivityCardHeader
|
||||
activity={activity}
|
||||
sport={sport}
|
||||
title={title}
|
||||
displayModal={() => this.displayModal(true)}
|
||||
/>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<div className="row">
|
||||
{activity.with_gpx && (
|
||||
<div className="col-8">
|
||||
<ActivityMap activity={activity} />
|
||||
<div>
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<ActivityCardHeader
|
||||
activity={activity}
|
||||
sport={sport}
|
||||
title={title}
|
||||
displayModal={() => this.displayModal(true)}
|
||||
/>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<div className="row">
|
||||
{activity.with_gpx && (
|
||||
<div className="col-8">
|
||||
<ActivityMap activity={activity} />
|
||||
</div>
|
||||
)}
|
||||
<div className="col">
|
||||
<ActivityDetails activity={activity} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<div className="card">
|
||||
<div className="card-body">
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<ActivityCharts activity={activity} />
|
||||
</div>
|
||||
)}
|
||||
<div className="col">
|
||||
<ActivityDetails activity={activity} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -29,4 +29,12 @@ export default class MpwoApi {
|
||||
return createRequest(params)
|
||||
}
|
||||
|
||||
static getActivityChartData(activityId) {
|
||||
const params = {
|
||||
url: `${apiUrl}activities/${activityId}/chart_data`,
|
||||
method: 'GET',
|
||||
}
|
||||
return createRequest(params)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -31,6 +31,15 @@ const activities = (state = initial.activities, action) => {
|
||||
}
|
||||
}
|
||||
|
||||
const chartData = (state = initial.chartData, action) => {
|
||||
switch (action.type) {
|
||||
case 'SET_CHART_DATA':
|
||||
return action.chartData
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
const formData = (state = initial.formData, action) => {
|
||||
switch (action.type) {
|
||||
case 'UPDATE_USER_FORMDATA':
|
||||
@ -163,6 +172,7 @@ const user = (state = initial.user, action) => {
|
||||
|
||||
const reducers = combineReducers({
|
||||
activities,
|
||||
chartData,
|
||||
formData,
|
||||
formProfile,
|
||||
gpx,
|
||||
|
@ -41,6 +41,7 @@ export default {
|
||||
activities: {
|
||||
...emptyData,
|
||||
},
|
||||
chartData: [],
|
||||
// check if storing gpx content is OK
|
||||
gpx: null,
|
||||
records: {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import togeojson from '@mapbox/togeojson'
|
||||
import { format, parse } from 'date-fns'
|
||||
import { format, parse, subHours } from 'date-fns'
|
||||
|
||||
export const apiUrl = `${process.env.REACT_APP_API_URL}/api/`
|
||||
export const thunderforestApiKey = `${
|
||||
@ -90,3 +90,17 @@ export const formatRecord = record => {
|
||||
value: value,
|
||||
}
|
||||
}
|
||||
|
||||
const formatDuration = seconds => {
|
||||
let newDate = new Date(0)
|
||||
newDate = subHours(newDate.setSeconds(seconds), 1)
|
||||
return newDate.getTime()
|
||||
}
|
||||
|
||||
export const formatChartData = chartData => {
|
||||
for (let i = 0; i < chartData.length; i++) {
|
||||
chartData[i].time = new Date(chartData[i].time).getTime()
|
||||
chartData[i].duration = formatDuration(chartData[i].duration)
|
||||
}
|
||||
return chartData
|
||||
}
|
||||
|
Reference in New Issue
Block a user