Okay, thank you for all your responses, I understand that maybe I do not have a good approach of Z2 training then. I will adapt my endurance training in order to target the middle of Z2.
I created the curve, I share you the script here, I think it can be useful anyway. Feel free to improve it.
{
function pad(str, size, car) {
while (str.length < size) str = car + str;
return str;
}
function duration_mm_ss(time) {
let time_m = Math.floor(time / 60);
let time_s = time - time_m * 60;
return pad(pad(time_m.toString(), 2, '0'), 3, ' ') + ":" + pad(time_s.toString(), 2, '0')
}
function get_pwrZones(power, zones) {
for (let i = 0; i < zones.length; i++) {
if (power <= zones[i]) return i+1;
}
return -1;
}
let activity = icu.activity;
let streams = icu.streams;
let pwr = streams.get("watts").data;
let d = streams.get("time").data;
let pwrMin = 30;
let pwrMax = 0;
for (let i = 0; i < pwr.length; i++) {
if (pwrMax < pwr[i]) {
pwrMax = pwr[i];
}
}
// Athlete power zones
colors = ['blue', 'green', 'yellow', 'orange', 'red', 'purple', 'grey', 'black']
zonesP = []
for (let i=0; i < activity.icu_power_zones.length; i++) {
zonesP.push(Math.min(pwrMax, Math.floor(activity.icu_ftp * activity.icu_power_zones[i] / 100)));
}
// Histogram
let totalTime = 0;
let watts_histo = [];
for (let i = 0; i <= pwrMax; i++) watts_histo.push(0);
for (let i = 0; i < pwr.length; i++) {
if (pwr[i] > pwrMin) {
watts_histo[pwr[i]] += 1;
totalTime += 1;
}
}
// Accumulation
let cumTime = 0;
let data_Pwr = [];
let data_Time = [];
let data_TimeStr = [];
let data_Text = [];
for (let i = pwrMin; i < watts_histo.length; i++) {
cumTime += watts_histo[i];
data_Pwr.push(i);
data_Time.push(cumTime);
data_TimeStr.push(duration_mm_ss(cumTime));
let percentUnder = cumTime / totalTime * 100;
let percentOver = 100 - cumTime / totalTime * 100;
let str = "<b>(Z" + get_pwrZones(i, zonesP) + ") " + i + " watts</b>"
+ "<br>" + duration_mm_ss(cumTime) + " Under (" + percentUnder.toFixed(1) + " %)"
+ "<br>" + duration_mm_ss(totalTime - cumTime) + " Over (" + percentOver.toFixed(1) + " %)"
;
data_Text.push(str);
}
// Shapes for power zones
shapesP = [{
type: 'rect',
xref: 'x',
yref: 'y',
x0: 0,
y0: 0,
x1: totalTime,
y1: zonesP[0],
layer: 'below',
opacity: 0.2,
fillcolor: colors[0],
line: {
color: 'black',
width: 0,
dash: 'dot'
}
}];
for (let i=0; i < activity.icu_power_zones.length-1; i++) {
temp_shape = {
type: 'rect',
xref: 'x',
yref: 'y',
x0: 0,
y0: zonesP[i]+1,
x1: totalTime,
y1: zonesP[i+1],
layer: 'below',
opacity: 0.2,
fillcolor: colors[i+1],
line: { width: 0 }
}
shapesP.push(temp_shape)
}
// Trace
trace1 = {
x: data_Time,
y: data_Pwr,
text: data_Text,
type: 'scatter',
yaxis: 'y',
xaxis: 'x',
name: 'Cumulative time under/over power',
mode: 'lines',
hoverinfo: 'text',
line: { color: 'black' }
};
var data = [trace1];
// Layout
layout = {
title: {
text: "Cumulative time under/over power",
font: { color: '#999999' },
},
xaxis: {
side: 'bottom',
range: [0, cumTime],
showline: true, zeroline: true,
autotick: false, tick0: 0, dtick: cumTime/10,
showspikes: true, spikemode: 'across', spikesnap: "cursor+data", spikedash: 'solid', spikethickness: 1,
color: '#999999',
},
yaxis: {
side: 'left',
range: [pwrMin, pwrMax+1],
showline: true, zeroline: true,
autotick: false, tick0: 0, dtick: 100,
showspikes: true, spikemode: 'across', spikesnap: "cursor+data", spikedash: 'solid', spikethickness: 1,
title: { text: 'Power', },
color: '#999999',
},
margin: { l: 50, r: 20, t: 30, b: 40 },
hovermode: 'x unified',
spikedistance: -1,
shapes: shapesP,
showlegend: false,
legend: {"orientation": "h"}
}
chart = { data, layout };
}