# Power Phase Diagram

**URL:** <https://forum.intervals.icu/t/power-phase-diagram/130630>\
**Category:** Questions & Help\
**Created:** [7 July 2026 19:01 UTC](https://forum.intervals.icu/t/power-phase-diagram/130630 "2026-07-07T19:01:03Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Andreas\_Schaudt](https://forum.intervals.icu/user_avatar/forum.intervals.icu/andreas_schaudt/32/42545_2.png) [@Andreas\_Schaudt](https://forum.intervals.icu/u/Andreas_Schaudt)\
**Post date:** [7 July 2026 19:01 UTC](https://forum.intervals.icu/t/power-phase-diagram/130630/1 "2026-07-07T19:01:03Z")

</div>

Hi all,

I recently have spent some time to create a power phase diagram:

 ![Screenshot 2026-07-07 201405](https://forum.intervals.icu/uploads/default/original/3X/c/5/c5c60b3370885db9aba8737c3ee83dda60eb5e50.png)

I am posting this as a guide on how to achieve this:

1. Make sure you have power phase L/R and peak power phase /LR dat in your fit file  
E.g. by uploading the original fit file to fitfile viewer.  
Note the power phase colums containing a pear of angles in degrees, one for the start and one for the end of the power phase.  

2. Make that data available to Intervals via custom data streams  
For the diagram we need one intervals data object (stream) for each of the 8 values listed above.  
Hence we need to create 8 custom streams to represent the data (ignore the data in brakets for now)

3. Reprocess the data for the activity  
To have the streams populated with data for the activity we need to reprocess the activity on intervals. From the activitys’ bottom menue → Actions → Reprocess File.

4. Create a custom chart

- add a new chart that is created with Javascript Code: From the activitys’ bottom menue → Activity Charts → Add → Ploty Script  
 ![Screenshot 2026-07-07 204809](https://forum.intervals.icu/uploads/default/original/3X/4/5/45a6619709b9f3e237454d4ea6f91feac1a8b75e.png)
- name your new chart  
 ![Screenshot 2026-07-07 205127](https://forum.intervals.icu/uploads/default/original/3X/6/d/6d086c6592576f2e10c13bce1eac086e1ffa3fa7.png)
- add the following Java Script Code to the editor in the Script Tab to create your chart:

> **Java Script Code**
>
> ```auto
> // 1. Fetch our 8 Garmin Custom Streams from Intervals.icu
> 
> const ppLStart = icu.streams.get("PowerPhaseLeftStart")?.data || \[\];
> 
> const ppLEnd = icu.streams.get("PowerPhaseLeftEnd")?.data || \[\];
> 
> const ppRStart = icu.streams.get("PowerPhaseRightStart")?.data || \[\];
> 
> const ppREnd = icu.streams.get("PowerPhaseRightEnd")?.data || \[\];
> 
> const pppLStart = icu.streams.get("PowerPhasePeakLeftStart")?.data || \[\];
> 
> const pppLEnd = icu.streams.get("PowerPhasePeakLeftEnd")?.data || \[\];
> 
> const pppRStart = icu.streams.get("PowerPhasePeakRightStart")?.data || \[\];
> 
> const pppREnd = icu.streams.get("PowerPhasePeakRightEnd")?.data || \[\];
> 
> // 2. Vector-Based Circular Mean Function (Trig Vector Averaging)
> 
> function getCircularAverage(angles) {
> 
> let validAngles = angles.filter(a => a !== null && a !== undefined && a !== 0);
> 
> if (validAngles.length === 0) return null;
> 
>  
> 
> let sinSum = 0;
> 
> let cosSum = 0;
> 
>  
> 
> for (let i = 0; i < validAngles.length; i++) {
> 
> let rad = (validAngles\[i\] \* Math.PI) / 180;
> 
> sinSum += Math.sin(rad);
> 
> cosSum += Math.cos(rad);
> 
> }
> 
>  
> 
> let avgRad = Math.atan2(sinSum / validAngles.length, cosSum / validAngles.length);
> 
> let avgDeg = (avgRad \* 180) / Math.PI;
> 
> return (avgDeg + 360) % 360;
> 
> }
> 
> // 3. Calculate Ranges and Centers
> 
> const avgLStart = getCircularAverage(ppLStart);
> 
> const avgLEnd = getCircularAverage(ppLEnd);
> 
> const leftRange = avgLStart !== null && avgLEnd !== null ? (avgLEnd - avgLStart + 360) % 360 : null;
> 
> const leftCenter = avgLStart !== null && leftRange !== null ? (avgLStart + leftRange / 2) % 360 : null;
> 
> const avgRStart = getCircularAverage(ppRStart);
> 
> const avgREnd = getCircularAverage(ppREnd);
> 
> const rightRange = avgRStart !== null && avgREnd !== null ? (avgREnd - avgRStart + 360) % 360 : null;
> 
> const rightCenter = avgRStart !== null && rightRange !== null ? (avgRStart + rightRange / 2) % 360 : null;
> 
> const avgLPeakStart = getCircularAverage(pppLStart);
> 
> const avgLPeakEnd = getCircularAverage(pppLEnd);
> 
> const leftPeakRange = avgLPeakStart !== null && avgLPeakEnd !== null ? (avgLPeakEnd - avgLPeakStart + 360) % 360 : null;
> 
> const leftPeakCenter = avgLPeakStart !== null && leftPeakRange !== null ? (avgLPeakStart + leftPeakRange / 2) % 360 : null;
> 
> const avgRPeakStart = getCircularAverage(pppRStart);
> 
> const avgRPeakEnd = getCircularAverage(pppREnd);
> 
> const rightPeakRange = avgRPeakStart !== null && avgRPeakEnd !== null ? (avgRPeakEnd - avgRPeakStart + 360) % 360 : null;
> 
> const rightPeakCenter = avgRPeakStart !== null && rightPeakRange !== null ? (avgRPeakStart + rightPeakRange / 2) % 360 : null;
> 
> // 4. Trace and Annotation setup
> 
> const traces = \[\];
> 
> const annotations = \[\];
> 
> const colors = {
> 
> leftMain: "rgba(54, 162, 235, 0.25)", 
> 
> leftMainBorder: "rgba(54, 162, 235, 0.6)",
> 
> leftPeak: "rgba(54, 162, 235, 0.85)", 
> 
> leftPeakBorder: "rgba(54, 162, 235, 1.0)",
> 
> rightMain: "rgba(255, 99, 132, 0.25)", 
> 
> rightMainBorder: "rgba(255, 99, 132, 0.6)",
> 
> rightPeak: "rgba(255, 99, 132, 0.85)", 
> 
> rightPeakBorder: "rgba(255, 99, 132, 1.0)"
> 
> };
> 
> // --- LEFT PEDAL PLOT ---
> 
> if (leftCenter !== null && leftRange !== null) {
> 
> traces.push({
> 
> type: "barpolar",
> 
> subplot: "polar",
> 
> name: "Left Power Phase",
> 
> r: \[1.0\],
> 
> theta: \[leftCenter\],
> 
> width: \[leftRange\],
> 
> marker: {
> 
> color: colors.leftMain,
> 
> line: { color: colors.leftMainBorder, width: 1.5 }
> 
> },
> 
> hoverinfo: "text",
> 
> text: \["Left Power Phase: " + avgLStart.toFixed(0) + "° - " + avgLEnd.toFixed(0) + "° (" + leftRange.toFixed(0) + "°)"\]
> 
> });
> 
> if (leftPeakCenter !== null && leftPeakRange !== null) {
> 
> traces.push({
> 
> type: "barpolar",
> 
> subplot: "polar",
> 
> name: "Left Peak Phase",
> 
> r: \[1.0\],
> 
> theta: \[leftPeakCenter\],
> 
> width: \[leftPeakRange\],
> 
> marker: {
> 
> color: colors.leftPeak,
> 
> line: { color: colors.leftPeakBorder, width: 1.5 }
> 
> },
> 
> hoverinfo: "text",
> 
> text: \["Left Peak Phase: " + avgLPeakStart.toFixed(0) + "° - " + avgLPeakEnd.toFixed(0) + "° (" + leftPeakRange.toFixed(0) + "°)"\]
> 
> });
> 
> }
> 
> traces.push({
> 
> type: "barpolar",
> 
> subplot: "polar",
> 
> r: \[0.6\], 
> 
> theta: \[0\],
> 
> width: \[360\],
> 
> marker: { color: "#ffffff" }, 
> 
> hoverinfo: "skip",
> 
> showlegend: false
> 
> });
> 
> annotations.push({
> 
> text: "<b>LEFT</b><br><span style='font-size:16px; font-weight:bold;'>" + leftRange.toFixed(0) + "°</span><br><span style='font-size:10px; color:#555;'>" + avgLStart.toFixed(0) + "°–" + avgLEnd.toFixed(0) + "°</span><br><span style='font-size:10px; color:#888;'>Peak: " + (leftPeakRange ? leftPeakRange.toFixed(0) : "N/A") + "°</span>",
> 
> xref: "paper", yref: "paper",
> 
> x: 0.22,
> 
> y: 0.5,
> 
> showarrow: false,
> 
> align: "center"
> 
> });
> 
> }
> 
> // --- RIGHT PEDAL PLOT ---
> 
> if (rightCenter !== null && rightRange !== null) {
> 
> traces.push({
> 
> type: "barpolar",
> 
> subplot: "polar2",
> 
> name: "Right Power Phase",
> 
> r: \[1.0\],
> 
> theta: \[rightCenter\],
> 
> width: \[rightRange\],
> 
> marker: {
> 
> color: colors.rightMain,
> 
> line: { color: colors.rightMainBorder, width: 1.5 }
> 
> },
> 
> hoverinfo: "text",
> 
> text: \["Right Power Phase: " + avgRStart.toFixed(0) + "° - " + avgREnd.toFixed(0) + "° (" + rightRange.toFixed(0) + "°)"\]
> 
> });
> 
> if (rightPeakCenter !== null && rightPeakRange !== null) {
> 
> traces.push({
> 
> type: "barpolar",
> 
> subplot: "polar2",
> 
> name: "Right Peak Phase",
> 
> r: \[1.0\],
> 
> theta: \[rightPeakCenter\],
> 
> width: \[rightPeakRange\],
> 
> marker: {
> 
> color: colors.rightPeak,
> 
> line: { color: colors.rightPeakBorder, width: 1.5 }
> 
> },
> 
> hoverinfo: "text",
> 
> text: \["Right Peak Phase: " + avgRPeakStart.toFixed(0) + "° - " + avgRPeakEnd.toFixed(0) + "° (" + rightPeakRange.toFixed(0) + "°)"\]
> 
> });
> 
> }
> 
> traces.push({
> 
> type: "barpolar",
> 
> subplot: "polar2",
> 
> r: \[0.6\],
> 
> theta: \[0\],
> 
> width: \[360\],
> 
> marker: { color: "#ffffff" }, 
> 
> hoverinfo: "skip",
> 
> showlegend: false
> 
> });
> 
> annotations.push({
> 
> text: "<b>RIGHT</b><br><span style='font-size:16px; font-weight:bold;'>" + rightRange.toFixed(0) + "°</span><br><span style='font-size:10px; color:#555;'>" + avgRStart.toFixed(0) + "°–" + avgREnd.toFixed(0) + "°</span><br><span style='font-size:10px; color:#888;'>Peak: " + (rightPeakRange ? rightPeakRange.toFixed(0) : "N/A") + "°</span>",
> 
> xref: "paper", yref: "paper",
> 
> x: 0.78,
> 
> y: 0.5,
> 
> showarrow: false,
> 
> align: "center"
> 
> });
> 
> }
> 
> if (traces.length === 0) {
> 
> annotations.push({
> 
> text: "No Power Phase data streams available for this activity.",
> 
> xref: "paper", yref: "paper", x: 0.5, y: 0.5,
> 
> showarrow: false, font: { size: 14 }
> 
> });
> 
> }
> 
> // 5. Final Chart Configuration
> 
> chart = {
> 
> data: traces,
> 
> layout: {
> 
> title: {
> 
> text: "<b>Pedal Power Phase (Left vs. Right)</b>",
> 
> font: { size: 16 }
> 
> },
> 
> showlegend: true,
> 
> legend: {
> 
> orientation: "h",
> 
> yanchor: "bottom",
> 
> y: -0.15,
> 
> xanchor: "center",
> 
> x: 0.5
> 
> },
> 
> barmode: "overlay", 
> 
> paper_bgcolor: "rgba(0,0,0,0)", 
> 
> plot_bgcolor: "rgba(0,0,0,0)",
> 
> margin: { t: 40, b: 60, l: 30, r: 30 },
> 
>    
> 
> polar: {
> 
> domain: { x: \[0.0, 0.45\], y: \[0.0, 1.0\] },
> 
> angularaxis: {
> 
> rotation: 90,        
> 
> direction: "clockwise",
> 
> tickvals: \[0, 90, 180, 270\],
> 
> ticktext: \["TDC (0°)", "90°", "BDC (180°)", "270°"\],
> 
> gridcolor: "#eeeeee",
> 
> linecolor: "#cccccc"
> 
> },
> 
> radialaxis: {
> 
> range: \[0.0, 1.0\],
> 
> visible: false 
> 
> }
> 
> },
> 
>    
> 
> polar2: {
> 
> domain: { x: \[0.55, 1.0\], y: \[0.0, 1.0\] },
> 
> angularaxis: {
> 
> rotation: 90,        
> 
> direction: "clockwise",
> 
> tickvals: \[0, 90, 180, 270\],
> 
> ticktext: \["TDC (0°)", "90°", "BDC (180°)", "270°"\],
> 
> gridcolor: "#eeeeee",
> 
> linecolor: "#cccccc"
> 
> },
> 
> radialaxis: {
> 
> range: \[0.0, 1.0\],
> 
> visible: false
> 
> }
> 
> },
> 
> annotations: annotations
> 
> }
> 
> };
> 
> ```

---

<div class="post-metadata">

**Author:** ![pepe](https://forum.intervals.icu/user_avatar/forum.intervals.icu/pepe/32/36539_2.png) [@pepe](https://forum.intervals.icu/u/pepe)\
**Post date:** [8 July 2026 05:20 UTC](https://forum.intervals.icu/t/power-phase-diagram/130630/2 "2026-07-08T05:20:09Z")

</div>

Hi Andreas, thank you for your script.

In relation to these custom streams, found that when using a UNO (single side(left)) powermeter, its best to use for:

1 LeftPowerPhaseStart

```auto
for (let m of icu.fit.record) {
    let f = m.f_69; 
    if (f && f.value !== undefined && f.value !== null) {
        let val = f.value;
        if (Array.isArray(val) && val[0] !== undefined) {
            data.setAt(m.timestamp.value, val[0]);
        } else if (typeof val === 'number') {
            data.setAt(m.timestamp.value, val);
        }
    }
}

```

2 LeftPowerPhaseEnd:

```auto
for (let m of icu.fit.record) {
    let f = m.f_69; 
    if (f && f.value !== undefined && f.value !== null) {
        let val = f.value;
        if (Array.isArray(val) && val.length > 1) {
            data.setAt(m.timestamp.value, val[1]);
        }
    }
}

```

3 LeftPowerPhasePeakStart

```auto
for (let m of icu.fit.record) {
    let f = m.f_70; 
    if (f && f.value !== undefined && f.value !== null) {
        let val = f.value;
        if (Array.isArray(val) && val[0] !== undefined) {
            data.setAt(m.timestamp.value, val[0]);
        } else if (typeof val === 'number') {
            data.setAt(m.timestamp.value, val);
        }
    }
}

```

4 LeftPowerPhasePeakEnd

```auto
for (let m of icu.fit.record) {
    let f = m.f_70; 
    if (f && f.value !== undefined && f.value !== null) {
        let val = f.value;
        if (Array.isArray(val) && val.length > 1) {
            data.setAt(m.timestamp.value, val[1]);
        }
    }
}

```

A safe fallback check to ensure it works flawlessly, without dropping out on missing records, as single side powermeter can omits messages on specific seconds if a full pedal revolution is not recorded. (updated)

---

<div class="post-metadata">

**Author:** ![Andreas\_Schaudt](https://forum.intervals.icu/user_avatar/forum.intervals.icu/andreas_schaudt/32/42545_2.png) [@Andreas\_Schaudt](https://forum.intervals.icu/u/Andreas_Schaudt)\
**Post date:** [8 July 2026 06:11 UTC](https://forum.intervals.icu/t/power-phase-diagram/130630/3 "2026-07-08T06:11:30Z")

</div>

> [@pepe](#):
>
> ermeter can omits messages on specific seconds if a full pedal revolution is not recorded.

Hi Pepe, thanks for you input, I’ll double check on my side if similar safe guards would be advisable for the dual sided ones 🙂

---

<div class="post-metadata">

**Author:** ![Dr\_Robert\_D\_Saunders](https://forum.intervals.icu/user_avatar/forum.intervals.icu/dr_robert_d_saunders/32/33768_2.png) [@Dr\_Robert\_D\_Saunders](https://forum.intervals.icu/u/Dr_Robert_D_Saunders)\
**Post date:** [9 July 2026 04:59 UTC](https://forum.intervals.icu/t/power-phase-diagram/130630/4 "2026-07-09T04:59:41Z")

</div>

Hi Andreas, Thank you for this: I’ve set this up with fit files from a Garmin Edge 840 and Assioma Pro RL dual sided power meter.

Just a couple of comments:

1. I initially named the Peak Power Phase custom streams PeakPowerPhaseLeftStart (and so on), but I noticed the chart script referred to “PowerPhasePeakLeftStart”, so I renamed the streams.
2. When I pasted the Chart script, it had a lot of back slash characters that I had to delete before the script would work.
3. The Chart didn’t work in the activity timeline, complaining about the colors variable already being declared. The chart worked fine under the Power tab of the activity.

Best wishes,

Robert

---

<div class="post-metadata">

**Author:** ![R2Tom](https://forum.intervals.icu/user_avatar/forum.intervals.icu/r2tom/32/50144_2.png) [@R2Tom](https://forum.intervals.icu/u/R2Tom)\
**Post date:** [9 July 2026 05:17 UTC](https://forum.intervals.icu/t/power-phase-diagram/130630/5 "2026-07-09T05:17:36Z")

</div>

I think it could be beneficial if @Andreas_Schaudt could make the scripts public, maybe e with the list of necessary custom scripts in the description of the chart.

---

<div class="post-metadata">

**Author:** ![pepe](https://forum.intervals.icu/user_avatar/forum.intervals.icu/pepe/32/36539_2.png) [@pepe](https://forum.intervals.icu/u/pepe)\
**Post date:** [9 July 2026 09:45 UTC](https://forum.intervals.icu/t/power-phase-diagram/130630/6 "2026-07-09T09:45:46Z")

</div>

Why not use the Garmin naming conventions

 ![imagem](https://forum.intervals.icu/uploads/default/original/3X/a/a/aaa160a1d95dd2fc7800076a7067305557f2d078.png)

In this case would be

LeftPowerPhasePeakStart

---

<div class="post-metadata">

**Author:** ![pepe](https://forum.intervals.icu/user_avatar/forum.intervals.icu/pepe/32/36539_2.png) [@pepe](https://forum.intervals.icu/u/pepe)\
**Post date:** [9 July 2026 10:52 UTC](https://forum.intervals.icu/t/power-phase-diagram/130630/7 "2026-07-09T10:52:24Z")

</div>

> [@Andreas\_Schaudt](#):
>
> I’ll double check on my side if similar safe guards would be advisable for the dual sided ones 🙂

DUO setups write data to every timestamp record, avoiding missing data issues. However, keep this difference in mind if you want to support both DUO and UNO configurations.

---

<div class="post-metadata">

**Author:** ![miguell](https://forum.intervals.icu/letter_avatar_proxy/v4/letter/m/85f322/32.png) [@miguell](https://forum.intervals.icu/u/miguell)\
**Post date:** [10 July 2026 05:02 UTC](https://forum.intervals.icu/t/power-phase-diagram/130630/8 "2026-07-10T05:02:05Z")

</div>

I also created a set of activity charts for Power Phase, Torque Effectiveness and Pco, in Activity Charts search keyword:

“Power Phase”

---

<div class="post-metadata">

**Author:** ![Andreas\_Schaudt](https://forum.intervals.icu/user_avatar/forum.intervals.icu/andreas_schaudt/32/42545_2.png) [@Andreas\_Schaudt](https://forum.intervals.icu/u/Andreas_Schaudt)\
**Post date:** [10 July 2026 08:50 UTC](https://forum.intervals.icu/t/power-phase-diagram/130630/9 "2026-07-10T08:50:15Z")

</div>

Thanks for the feedback! I will think about a clearer namich schema and proper variable name space. I in fakt only tested it on a dedicated tab i called “Cycling dynamics” and missed the name/variable overlaps.

---

<div class="post-metadata">

**Author:** ![Andreas\_Schaudt](https://forum.intervals.icu/user_avatar/forum.intervals.icu/andreas_schaudt/32/42545_2.png) [@Andreas\_Schaudt](https://forum.intervals.icu/u/Andreas_Schaudt)\
**Post date:** [10 July 2026 08:50 UTC](https://forum.intervals.icu/t/power-phase-diagram/130630/10 "2026-07-10T08:50:58Z")

</div>

Good Point. I’ll have to take a look into the sharing options for streams and Charts after I fixed some of the potential namespace problems.

---

<div class="post-metadata">

**Author:** ![Andreas\_Schaudt](https://forum.intervals.icu/user_avatar/forum.intervals.icu/andreas_schaudt/32/42545_2.png) [@Andreas\_Schaudt](https://forum.intervals.icu/u/Andreas_Schaudt)\
**Post date:** [10 July 2026 08:51 UTC](https://forum.intervals.icu/t/power-phase-diagram/130630/11 "2026-07-10T08:51:54Z")

</div>

> [@pepe](#):
>
> want to support both DUO and UNO configur

Good point. I think I could be more consisten in my naming. I’ll fix this in the next iteration.

---

<div class="post-metadata">

**Author:** ![miguell](https://forum.intervals.icu/letter_avatar_proxy/v4/letter/m/85f322/32.png) [@miguell](https://forum.intervals.icu/u/miguell)\
**Post date:** [10 July 2026 14:16 UTC](https://forum.intervals.icu/t/power-phase-diagram/130630/12 "2026-07-10T14:16:08Z")

</div>

My favourite activity chart from the set:

“Biomechanical Power-Loss Profiler”

 ![image](https://forum.intervals.icu/uploads/default/original/3X/c/3/c34324041853c18f46d6f225fd7390f0b9877563.png)

---

<div class="post-metadata">

**Author:** ![miguell](https://forum.intervals.icu/letter_avatar_proxy/v4/letter/m/85f322/32.png) [@miguell](https://forum.intervals.icu/u/miguell)\
**Post date:** [11 July 2026 14:09 UTC](https://forum.intervals.icu/t/power-phase-diagram/130630/13 "2026-07-11T14:09:59Z")

</div>

Refreshed the script with fully dynamic adaptive annotations. At the request of some users, the following modes have been added: Road Triathlon and Aero:

 ![image](https://forum.intervals.icu/uploads/default/original/3X/4/6/46fd0690e2b63e86fa9b2fc0dcda1fa2960ca9b3.png)

Custom Streams requirement:

Left Power Phase Start  
Left Power Phase End  
Left Power Phase Peak Start  
Left Power Phase Peak End  
Right Power Phase Start  
Right Power Phase End  
Right Power Phase Peak Start  
Right Power Phase Peak End  
Left Pco  
Right Pco

Optional: Force (Newtons)

---

<div class="post-metadata">

**Author:** ![pepe](https://forum.intervals.icu/user_avatar/forum.intervals.icu/pepe/32/36539_2.png) [@pepe](https://forum.intervals.icu/u/pepe)\
**Post date:** [11 July 2026 14:47 UTC](https://forum.intervals.icu/t/power-phase-diagram/130630/14 "2026-07-11T14:47:59Z")

</div>

Thank you @miguell, that’s a powerful app.

---

<div class="post-metadata">

**Author:** ![miguell](https://forum.intervals.icu/letter_avatar_proxy/v4/letter/m/85f322/32.png) [@miguell](https://forum.intervals.icu/u/miguell)\
**Post date:** [12 July 2026 15:16 UTC](https://forum.intervals.icu/t/power-phase-diagram/130630/15 "2026-07-12T15:16:10Z")

</div>

Thank you @Andreas_Schaudt and @pepe for the Custom Streams, all kudos to you.

---

<div class="post-metadata">

**Author:** ![miguell](https://forum.intervals.icu/letter_avatar_proxy/v4/letter/m/85f322/32.png) [@miguell](https://forum.intervals.icu/u/miguell)\
**Post date:** [12 July 2026 15:23 UTC](https://forum.intervals.icu/t/power-phase-diagram/130630/16 "2026-07-12T15:23:08Z")

</div>

By the way new: Biomechanical Power-Loss Profiler version with some Single Sided bugs corrected.

and added a NEW Activity Chart named:

Pedal Stroke “Dead Zone” Profiler with Recommendations \*

---

<div class="post-metadata">

**Author:** ![miguell](https://forum.intervals.icu/letter_avatar_proxy/v4/letter/m/85f322/32.png) [@miguell](https://forum.intervals.icu/u/miguell)\
**Post date:** [13 July 2026 15:33 UTC](https://forum.intervals.icu/t/power-phase-diagram/130630/17 "2026-07-13T15:33:02Z")

</div>

### 🚀 Biomechanical Power-Loss Profiler

If you use pedal-based power meters like Garmin Rally/Vector or Favero Assioma, this script generates a **360° Polar Chart** that reveals the hidden mechanics of your pedal stroke.

Here is exactly what this script is good for:

#### 1. Identifying “Dead Spots” & Wasted Energy

It maps your power phase against a **Braking/Drag Zone (Red)**. This allows you to see if your recovering leg is acting as “dead weight” on the upstroke, forcing your dominant leg to push harder just to overcome your own body weight.

#### 2. Position-Specific Fit Diagnostics (Road, Tri, Aero)

The script doesn’t just look at your data in a vacuum—it overlays a **Target Window (Green)** based on your riding position. It runs your numbers against three distinct biomechanical profiles to tell you if your bike fit is dialed:

- **Road Mode:** Tailored for an upright hoods/tops position. It expects an early power phase startup (350∘) and a peak torque zone firing early (45∘ to 65∘) due to your open hip angle. It is the Classic Road settings.

- **Triathlon Mode:** Adjusted for steep seat tube angles where your hips are shifted forward over the bottom bracket. The script shifts its expectations clockwise, looking for a later peak torque zone (80∘ to 110∘). With Canyon Speedmax CFR most triathletes set this bike up to achieve an **effective seat tube angle of 79° to 82°**. **Triathlon Mode** (with peak torque range **80°–110°** ) is perfectly aligned with this setup. By achieving that steeper effective angle, you are rotating your pelvis forward, which naturally moves your “sweet spot” for force production later in the pedal stroke compared to a standard road bike.

- **Aero TT Mode** : Designed for deep drops or low aero bar tucks that cause significant hip-angle collapse. It monitors for pedaling ‘latency’ at the top of the stroke, where the hip angle is most restricted, and provides feedback to optimize peak torque to the 90°–120° range to maintain power-phase fluidity

#### 3. Fine-Tuning Your Cleat Position & Bike Fit

By visually overlaying your **Peak Torque Zone (Orange)** inside your **Total Power Phase (Blue)**, you can see exactly where your muscles fire hardest. If your personal peak torque falls completely outside the green position targets (e.g., hitting at 75∘ in Road Mode), the script flags a `❌ LATE RECRUITMENT` error, serving as a diagnostic warning to adjust your saddle height or fore-aft position.

#### 4. Fixing Left/Right Biomechanical Imbalances

While standard bike computers just give you a flat percentage (like 48%/52%), this script reveals the **why**. You can see if one leg has a shorter power phase, if a leg initiates power too late, or if one foot is unstable during the transition phases.

#### 5. Tracking Fatigue Across Different Durations

Thanks to the moving-window feature, you can toggle between your peak 60-second sprint and a steady 20-minute threshold effort. This shows you how your pedalling technique and leg symmetry hold up when you are fresh versus when your neural pathways and muscles are deeply fatigued under different structural demands.

---

<div class="post-metadata">

**Author:** ![Andreas\_Schaudt](https://forum.intervals.icu/user_avatar/forum.intervals.icu/andreas_schaudt/32/42545_2.png) [@Andreas\_Schaudt](https://forum.intervals.icu/u/Andreas_Schaudt)\
**Post date:** [14 July 2026 10:22 UTC](https://forum.intervals.icu/t/power-phase-diagram/130630/18 "2026-07-14T10:22:01Z")

</div>

Man that is fantastic!!! Thanks a lot!

---

<div class="post-metadata">

**Author:** ![DSlipstream](https://forum.intervals.icu/user_avatar/forum.intervals.icu/dslipstream/32/78030_2.png) [@DSlipstream](https://forum.intervals.icu/u/DSlipstream)\
**Post date:** [14 July 2026 15:00 UTC](https://forum.intervals.icu/t/power-phase-diagram/130630/19 "2026-07-14T15:00:52Z")

</div>

Curious. What does this do? I have power pedals. And, have all this information. And, I’m lopsided. oh wow. This explanation from miguell is very helpful in understanding and interpreting this data. Yes please. I’ll try to figure out how to get this data loaded. thanks for this!

---

<div class="post-metadata">

**Author:** ![Rconn](https://forum.intervals.icu/user_avatar/forum.intervals.icu/rconn/32/86517_2.png) [@Rconn](https://forum.intervals.icu/u/Rconn)\
**Post date:** [14 July 2026 20:55 UTC](https://forum.intervals.icu/t/power-phase-diagram/130630/20 "2026-07-14T20:55:00Z")

</div>

How do I set this up in my charts? Found out I have a 12% difference in my legs.

[Next page](https://forum.intervals.icu/t/power-phase-diagram/130630.md?page=2)
