Is this what you are looking for?
// --- Configuration ---
const colors = [
'rgb(173, 216, 230)', // Z1 - Light Blue
'rgb(0, 128, 0)', // Z2 - Green
'rgb(255, 215, 0)', // Z3 - Yellow
'rgb(255, 140, 0)', // Z4 - Orange
'rgb(255, 0, 0)', // Z5 - Red
'rgb(128, 0, 128)', // Z6 - Purple
'rgb(0, 0, 0)', // Z7 - Black
'rgb(169, 169, 169)', // Unclassified - Gray
'rgb(0, 191, 255)' // Sweet Spot - Deep Sky Blue
];
// --- Data Extraction ---
const act = icu.activity;
const ftp = act.icu_ftp;
const zones = act.icu_power_zones; // Expected: [55, 75, 90, 105, 120, 150]
const powerStream = icu.streams.get("fixed_watts")?.data || [];
// --- Zone Definitions ---
const zoneDefs = [
{ name: 'Z1', lower: 0.0, upper: zones[0] / 100 },
{ name: 'Z2', lower: zones[0] / 100, upper: zones[1] / 100 },
{ name: 'Z3', lower: zones[1] / 100, upper: zones[2] / 100 },
{ name: 'Z4', lower: zones[2] / 100, upper: zones[3] / 100 },
{ name: 'Z5', lower: zones[3] / 100, upper: zones[4] / 100 },
{ name: 'Z6', lower: zones[4] / 100, upper: zones[5] / 100 },
{ name: 'Z7', lower: zones[5] / 100, upper: Infinity }
];
const bounds = zoneDefs.map(z => ({
name: z.name,
lower: ftp * z.lower,
upper: ftp * z.upper
}));
// --- Energy in Zones ---
let joules = new Array(8).fill(0); // 7 zones + 1 unclassified
powerStream.forEach(w => {
if (w == null) return;
let classified = false;
for (let i = 0; i < 7; i++) {
if (w >= bounds[i].lower && w < bounds[i].upper) {
joules[i] += w;
classified = true;
break;
}
}
if (!classified) {
joules[7] += w; // Unclassified
}
});
let kJ = joules.map(j => j / 1000);
let trueTotalKJ = powerStream.reduce((sum, w) => sum + (w || 0), 0) / 1000;
let perc = kJ.map(j => trueTotalKJ > 0 ? (j / trueTotalKJ) * 100 : 0);
// --- Sweet Spot Zone ---
const sweetSpotLower = ftp * 0.84;
const sweetSpotUpper = ftp * 0.97;
let sweetSpotJoules = 0;
powerStream.forEach(w => {
if (w == null) return;
if (w >= sweetSpotLower && w < sweetSpotUpper) {
sweetSpotJoules += w;
}
});
const sweetSpotKJ = sweetSpotJoules / 1000;
const sweetSpotPerc = trueTotalKJ > 0 ? (sweetSpotKJ / trueTotalKJ) * 100 : 0;
// --- Plotly Traces ---
let traces = [];
for (let i = 0; i < 8; i++) {
let label = i < 7 ? `Zone ${i + 1}` : "Unclassified";
let lower = i < 7 ? Math.round(bounds[i].lower) : "<" + Math.round(bounds[0].lower);
let upper = i < 7 ? (bounds[i].upper === Infinity ? "∞" : Math.round(bounds[i].upper)) : "";
traces.push({
x: [i + 1],
y: [kJ[i]],
orientation: 'v',
name: label,
type: 'bar',
marker: { color: colors[i] },
hovertemplate: `<b>${label}</b><br>${lower}${upper ? "–" + upper : ""} watts<br>${perc[i].toFixed(1)}% of total energy<br>${kJ[i].toFixed(1)} kJ<extra></extra>`
});
}
// --- Sweet Spot Trace ---
traces.push({
x: [9],
y: [sweetSpotKJ],
orientation: 'v',
name: 'Sweet Spot',
type: 'bar',
marker: { color: colors[8] },
hovertemplate: `<b>Sweet Spot Zone</b><br>${Math.round(sweetSpotLower)}–${Math.round(sweetSpotUpper)} watts<br>${sweetSpotPerc.toFixed(1)}% of total energy<br>${sweetSpotKJ.toFixed(1)} kJ<extra></extra>`
});
// --- Layout ---
let powerZoneLayout = {
height: 300,
title: {
text: `Energy Spent in Power Zones<br><sup>FTP: ${ftp} watts — Total: ${trueTotalKJ.toFixed(1)} kJ</sup>`,
xref: 'paper',
x: 0.5
},
margin: { b: 50, l: 50, r: 30, t: 90 },
barmode: 'stack',
hoverlabel: {
align: 'left',
bgcolor: 'white',
font: { color: 'black' }
},
hovermode: 'closest',
showlegend: false,
yaxis: {
title: "Energy (kJ)",
showgrid: true,
zeroline: true
},
xaxis: {
zeroline: false,
showgrid: false,
tickvals: [1, 2, 3, 4, 5, 6, 7, 8, 9],
ticktext: ['Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'Z6', 'Z7+', 'Unclassified', 'Sweet Spot']
}
};
// --- Final Chart ---
let chart = { data: traces, layout: powerZoneLayout };
chart;