Building on top of from my work custom streams showing gear selection over time on the Activity screen, I’ve created a chart showing a gear selection heat map.
To make this work, I believe you first have to first add the “Rear Gear” and “Front Gear” custom streams (see above) and re-process your activity (or upload new ones).
A few comments out of the gate:
- Since this looks only at the recorded data, I cannot chart gears that weren’t used in the activity. E.g. if you were never in the small chain ring, or if you never used the smallest cog the chart will not show that
- This looks at all time, not pedalling time (which might be more interesting)
- This is probably not the most interesting visualisation for this data
- Getting some “Memory limit exceeded” on longer activities (4h+). I tried a few things, but couldn’t really figure out where the memory went.
I hope you can make something even cooler that builds upon this. The code is below.
Javascript code for the custom chart
{
function unique(val, idx, arr) {
return arr.indexOf(val) === idx
}
function nonNull(val, idx, arr) {
return val !== null
}
function format_seconds(s) {
var sec_num = parseInt(s, 10); // don't forget the second param
var hours = Math.floor(sec_num / 3600);
var minutes = Math.floor((sec_num - (hours * 3600)) / 60);
var seconds = sec_num - (hours * 3600) - (minutes * 60);
if (hours > 0 && minutes < 10) {minutes = "0"+minutes;}
if (seconds < 10) {seconds = "0"+seconds;}
return hours > 0 ? hours+':'+minutes+':'+seconds : minutes+':'+seconds;
}
let rear_gear = icu.streams.get('RearGear');
let front_gear = icu.streams.get('FrontGear');
if (rear_gear == null || front_gear == null) {
let data = [];
let layout = {
title: {
text: "Gear heatmap - Missing data",
font: { color: 'black', size: 20 }
},
xaxis: { visible: false },
yaxis: { visible: false }
};
chart = { data, layout };
}
else {
// This logic means we cannot show gears which were never used
// e.g. if the small ring or the smallest sprocket were never used, a row or column will be "missing"
var rear_gears = rear_gear.data.filter(nonNull).filter(unique).sort();
var front_gears = front_gear.data.filter(nonNull).filter(unique).sort().reverse();
// Create zeroed 2D array to hold Z values
let z = Array(front_gears.length).fill().map(() => Array(rear_gears.length).fill(0));
// Count seconds in each gear into Z
for (let i=0; i < rear_gear.data.length; i++) {
let f = front_gear.data[i]
let r = rear_gear.data[i]
if (!f || !r) continue;
z[front_gears.indexOf(f)][rear_gears.indexOf(r)] += 1;
}
// Create an array with hover texts (seconds formatted as HH:MM:SS)
let text = []
for (let i=0; i < z.length; i++) {
let row = z[i];
text.push(row.map(format_seconds));
}
let data = [
{
z: z,
x: rear_gears,
y: front_gears,
text: text,
type: 'heatmap',
hoverongaps: false,
hovertemplate: '%{y}x%{x}: %{text}<extra></extra>',
colorscale: 'Viridis',
showscale: false
}
];
let layout = {
title: {
text: "Gear heatmap",
font: { color: 'black', size: 20 }
},
yaxis: { type: 'category', ticks: '' },
xaxis: { type: 'category', ticks: '' }
}
chart = { data, layout }
}
}