Skip to content
This repository was archived by the owner on May 8, 2025. It is now read-only.

Commit d681503

Browse files
committed
added quadrantAnalysis()
1 parent 2d26fe6 commit d681503

File tree

4 files changed

+197
-9
lines changed

4 files changed

+197
-9
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,10 +286,11 @@ $pFFA->hrMetrics($hr_resting, $hr_maximum, $hr_FT, $gender);
286286
* Intensity Factor
287287

288288
###Power
289-
Two functions exist for analysing power data:
289+
Three functions exist for analysing power data:
290290
```php
291291
$pFFA->powerMetrics($functional_threshold_power); // e.g. 312
292292
$pFFA->criticalPower($time_periods); // e.g. 300 or [300, 600, 900, 1200]
293+
$pFFA->quadrantAnalysis($crank_length, $ftp, $selected_cadence); // Crank length in metres; cadence defaults to 90rpm if not supplied
293294
```
294295
**Power metrics:**
295296
* Average Power
@@ -303,6 +304,9 @@ $pFFA->criticalPower($time_periods); // e.g. 300 or [300, 600, 900, 1200]
303304

304305
Note that ```$pFFA->criticalPower``` and some power metrics (Normalised Power, Variability Index, Intensity Factor, Training Stress Score) will use the [PHP Trader](http://php.net/manual/en/book.trader.php) extension if it is loaded on the server. If the extension is not loaded then it will use the built-in Simple Moving Average algorithm, which is far less performant particularly for larger files!
305306

307+
**Quadrant Analysis** provides insight into the neuromuscular demands of a bike ride through comparing pedal velocity with force by looking at cadence and power.
308+
![Mountain Biking](demo/img/quadrant-analysis.jpg)
309+
306310
A demo of power analysis is available [here](http://www.adriangibbons.com/php-fit-file-analysis/demo/power-analysis.php).
307311

308312
##Other methods

demo/img/quadrant-analysis.jpg

24.7 KB
Loading

demo/power-analysis.php

Lines changed: 120 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
$file = '/fit_files/power-analysis.fit';
1212

1313
$options = [
14-
// 'fixData' => ['all'],
15-
'units' => ['metric']
14+
// 'fixData' => ['all'],
15+
// 'units' => ['metric']
1616
];
1717
$pFFA = new adriangibbons\phpFITFileAnalysis(__DIR__ . $file, $options);
1818

@@ -28,13 +28,15 @@
2828
$date_s = $date_s + $json_tz->rawOffset + $json_tz->dstOffset;
2929
}
3030
$date->setTimestamp($date_s);
31-
31+
32+
$ftp = 329;
3233
$hr_metrics = $pFFA->hrMetrics(52, 185, 172, 'male');
33-
$power_metrics = $pFFA->powerMetrics(312);
34+
$power_metrics = $pFFA->powerMetrics($ftp);
3435
$criticalPower = $pFFA->criticalPower([2,3,5,10,30,60,120,300,600,1200,3600,7200,10800,18000]);
3536
$power_histogram = $pFFA->powerHistogram();
36-
$power_table = $pFFA->powerPartioned(312);
37-
$power_pie_chart = $pFFA->partitionData('power', $pFFA->powerZones(312), true, false);
37+
$power_table = $pFFA->powerPartioned($ftp);
38+
$power_pie_chart = $pFFA->partitionData('power', $pFFA->powerZones($ftp), true, false);
39+
$quad_plot = $pFFA->quadrantAnalysis(0.175, $ftp);
3840
} catch (Exception $e) {
3941
echo 'caught exception: '.$e->getMessage();
4042
die();
@@ -152,6 +154,16 @@
152154
</div>
153155
</div>
154156
</div>
157+
158+
<div class="panel panel-default">
159+
<div class="panel-heading">
160+
<h3 class="panel-title"><i class="fa fa-line-chart"></i> Quadrant Analysis <small>Circumferential Pedal Velocity (x-axis) vs Average Effective Pedal Force (y-axis)</small></h3>
161+
</div>
162+
<div class="panel-body">
163+
<div id="quadrant_analysis" style="width:100%; height:600px"></div>
164+
</div>
165+
</div>
166+
155167
</div>
156168
</div>
157169
</div>
@@ -370,6 +382,108 @@ function labelFormatter(label, series) {
370382
$("#power_zones_table tr").removeClass("danger");
371383
$("#" + obj.series.data[0][1].toFixed(1).toString().replace(/\./g, '-') ).addClass("danger");
372384
});
385+
386+
var quad = [<?php
387+
$plottmp = [];
388+
foreach ($quad_plot['plot'] as $v) {
389+
$plottmp[] = '[' . $v[0] . ', ' . $v[1] . ']';
390+
}
391+
echo implode(', ', $plottmp); ?>];
392+
393+
var lo = [<?php
394+
unset ($plottmp);
395+
$plottmp = [];
396+
foreach ($quad_plot['ftp-25w'] as $v) {
397+
$plottmp[] = '[' . $v[0] . ', ' . $v[1] . ']';
398+
}
399+
echo implode(', ', $plottmp); ?>];
400+
401+
var at = [<?php
402+
unset ($plottmp);
403+
$plottmp = [];
404+
foreach ($quad_plot['ftp'] as $v) {
405+
$plottmp[] = '[' . $v[0] . ', ' . $v[1] . ']';
406+
}
407+
echo implode(', ', $plottmp); ?>];
408+
var hi = [<?php
409+
unset ($plottmp);
410+
$plottmp = [];
411+
foreach ($quad_plot['ftp+25w'] as $v) {
412+
$plottmp[] = '[' . $v[0] . ', ' . $v[1] . ']';
413+
}
414+
echo implode(', ', $plottmp); ?>];
415+
416+
var markings = [
417+
{
418+
color: "black",
419+
lineWidth: 1,
420+
xaxis: {
421+
from: <?php echo $quad_plot['cpv_threshold']; ?>,
422+
to: <?php echo $quad_plot['cpv_threshold']; ?>
423+
}
424+
},
425+
{
426+
color: "black",
427+
lineWidth: 1,
428+
yaxis: {
429+
from: <?php echo $quad_plot['aepf_threshold']; ?>,
430+
to: <?php echo $quad_plot['aepf_threshold']; ?>
431+
}
432+
}
433+
];
434+
435+
var quadrant_analysis_options = {
436+
xaxis: {
437+
label: 'circumferential pedal velocity',
438+
tickFormatter: function(label, series) { return label + ' m/s'; }
439+
},
440+
yaxis: {
441+
max: 400,
442+
label: 'average effective pedal force',
443+
tickSize: 50,
444+
tickFormatter: function(label, series) {
445+
if(label == 0) return "";
446+
return label + ' N';
447+
}
448+
},
449+
grid: {
450+
borderWidth: {
451+
top: 0,
452+
right: 0,
453+
bottom: 0,
454+
left: 0
455+
},
456+
markings: markings
457+
},
458+
legend: { show: false }
459+
};
460+
461+
var plot_qa = $.plot($("#quadrant_analysis"), [
462+
{
463+
data : quad,
464+
points : { show: true, radius: 0.25, fill : true, fillColor: "#058DC7" }
465+
},
466+
{
467+
data : at,
468+
color: "blue",
469+
lines: { show: true, lineWidth: 0.5 }
470+
},
471+
{
472+
data : lo,
473+
color: "red",
474+
lines: { show: true, lineWidth: 0.5 }
475+
},
476+
{
477+
data : hi,
478+
color: "green",
479+
lines: { show: true, lineWidth: 0.5 }
480+
}
481+
], quadrant_analysis_options);
482+
483+
$("#quadrant_analysis").append("<span style='background-color: #fafafa; top: " + (plot_qa.height() / 2 - 40) + "px; color: #333; text-align: center; font-size: 12px; border: 1px solid #ddd; border-radius: 5px; padding: 3px 7px; position: absolute; left: " + (plot_qa.width() - 140) + "px'><strong>High Force / High Velocity</strong><br><?php echo $quad_plot['quad_percent']['hf_hv']; ?> %</span>");
484+
$("#quadrant_analysis").append("<span style='background-color: #fafafa; top: " + (plot_qa.height() / 2 - 40) + "px; color: #333; text-align: center; font-size: 12px; border: 1px solid #ddd; border-radius: 5px; padding: 3px 7px; position: absolute; left: 50px'><strong>High Force / Low Velocity</strong><br><?php echo $quad_plot['quad_percent']['hf_lv']; ?> %</span>");
485+
$("#quadrant_analysis").append("<span style='background-color: #fafafa; top: " + (plot_qa.height() / 2 + 15) + "px; color: #333; text-align: center; font-size: 12px; border: 1px solid #ddd; border-radius: 5px; padding: 3px 7px; position: absolute; left: 50px'><strong>Low Force / Low Velocity</strong><br><?php echo $quad_plot['quad_percent']['lf_lv']; ?> %</span>");
486+
$("#quadrant_analysis").append("<span style='background-color: #fafafa; top: " + (plot_qa.height() / 2 + 15) + "px; color: #333; text-align: center; font-size: 12px; border: 1px solid #ddd; border-radius: 5px; padding: 3px 7px; position: absolute; left: " + (plot_qa.width() - 140) + "px'><strong>Low Force / High Velocity</strong><br><?php echo $quad_plot['quad_percent']['lf_hv']; ?> %</span>");
373487
});
374488
</script>
375489
</body>

src/phpFITFileAnalysis.php

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
* Adrian Gibbons, 2015
99
* Adrian.GitHub@gmail.com
1010
*
11-
* G Frogley edits, June 2016
12-
* Added code to generate TRIMPexp and hrIF (Intensity Factor) value to measure session if Power is not present
11+
* G Frogley edits:
12+
* Added code to generate TRIMPexp and hrIF (Intensity Factor) value to measure session if Power is not present (June 2015).
13+
* Added code to generate Quadrant Analysis data (September 2015).
1314
*
1415
* https://github.com/adriangibbons/phpFITFileAnalysis
1516
* http://www.thisisant.com/resources/fit
@@ -1851,6 +1852,75 @@ public function isPaused()
18511852
return $is_paused;
18521853
}
18531854

1855+
/**
1856+
* Returns an array that can be used to plot Circumferential Pedal Velocity (x-axis) vs Average Effective Pedal Force (y-axis).
1857+
* NB Crank length is in metres.
1858+
*/
1859+
public function quadrantAnalysis($crank_length, $ftp, $selected_cadence = 90)
1860+
{
1861+
if (!isset($this->data_mesgs['record']['power'])) {
1862+
throw new \Exception('phpFITFileAnalysis->quadrantAnalysis(): power data not present in FIT file!');
1863+
}
1864+
if (!isset($this->data_mesgs['record']['cadence'])) {
1865+
throw new \Exception('phpFITFileAnalysis->quadrantAnalysis(): cadence data not present in FIT file!');
1866+
}
1867+
1868+
$quadrant_plot = [];
1869+
$quadrant_plot['selected_cadence'] = $selected_cadence;
1870+
$quadrant_plot['aepf_threshold'] = round(($ftp * 60) / ($selected_cadence * 2 * pi() * $crank_length), 3);
1871+
$quadrant_plot['cpv_threshold'] = round(($selected_cadence * $crank_length * 2 * pi()) / 60, 3);
1872+
1873+
// Used to calculate percentage of points in each quadrant
1874+
$quad_percent = ['hf_hv' => 0, 'hf_lv' => 0, 'lf_lv' => 0, 'lf_hv' => 0];
1875+
1876+
// Filter zeroes from cadence array (otherwise !div/0 error for AEPF)
1877+
$cadence = array_filter($this->data_mesgs['record']['cadence']);
1878+
$cpv = $aepf = 0.0;
1879+
1880+
foreach ($cadence as $k => $c) {
1881+
$p = isset($this->data_mesgs['record']['power'][$k]) ? $this->data_mesgs['record']['power'][$k] : 0;
1882+
1883+
// Circumferential Pedal Velocity (CPV, m/s) = (Cadence × Crank Length × 2 × Pi) / 60
1884+
$cpv = round(($c * $crank_length * 2 * pi()) / 60, 3);
1885+
1886+
// Average Effective Pedal Force (AEPF, N) = (Power × 60) / (Cadence × 2 × Pi × Crank Length)
1887+
$aepf = round(($p * 60) / ($c * 2 * pi() * $crank_length), 3);
1888+
1889+
$quadrant_plot['plot'][] = [$cpv, $aepf];
1890+
1891+
if ($aepf > $quadrant_plot['aepf_threshold']) { // high force
1892+
if ($cpv > $quadrant_plot['cpv_threshold']) { // high velocity
1893+
$quad_percent['hf_hv']++;
1894+
} else {
1895+
$quad_percent['hf_lv']++;
1896+
}
1897+
} else { // low force
1898+
if ($cpv > $quadrant_plot['cpv_threshold']) { // high velocity
1899+
$quad_percent['lf_hv']++;
1900+
} else {
1901+
$quad_percent['lf_lv']++;
1902+
}
1903+
}
1904+
}
1905+
1906+
// Convert to percentages and add to array that will be returned by the function
1907+
$sum = array_sum($quad_percent);
1908+
foreach ($quad_percent as $k => $v) {
1909+
$quad_percent[$k] = round($v / $sum * 100, 2);
1910+
}
1911+
$quadrant_plot['quad_percent'] = $quad_percent;
1912+
1913+
// Calculate CPV and AEPF for cadences between 20 and 150rpm at and near to FTP
1914+
for ($c = 20; $c <= 150; $c += 5) {
1915+
$cpv = round((($c * $crank_length * 2 * pi()) / 60), 3);
1916+
$quadrant_plot['ftp-25w'][] = [$cpv, round((($ftp - 25) * 60) / ($c * 2 * pi() * $crank_length), 3)];
1917+
$quadrant_plot['ftp'][] = [$cpv, round(($ftp * 60) / ($c * 2 * pi() * $crank_length), 3)];
1918+
$quadrant_plot['ftp+25w'][] = [$cpv, round((($ftp + 25) * 60) / ($c * 2 * pi() * $crank_length), 3)];
1919+
}
1920+
1921+
return $quadrant_plot;
1922+
}
1923+
18541924
/**
18551925
* Outputs tables of information being listened for and found within the processed FIT file.
18561926
*/

0 commit comments

Comments
 (0)