HTML Chart in train view

240 views
Skip to first unread message

Peret

unread,
Jun 18, 2026, 10:36:33 AM (9 days ago) Jun 18
to golden-cheetah-users

I have made a Pull Request introduces a new functionality for the Train view: the ability to add custom charts based on HTML/JS source code, linked in real-time with the training telemetry.

With this feature, athletes are no longer limited to predefined charts or telemetry fields when training in GC. They can now design awesome, dynamic charts that react to live telemetry—such as histograms, maps, dials, and more. Even without extensive knowledge of HTML/JS, users can easily achieve great results by leveraging generic web design tools or AI-based HTML code generators.

For developers of html, the PR HTML Chart in train view documents its usage. It is nos complete, and until it is (hopefully) in GC binary, that documentation can be a starting point. Afterwards, GC wiki will have the documentation.

In this PR, Python charts for training and Web charts hability to use telemetry updates in training functionalities have not been included, for the sake of clarity of the PR and not mixing functionalities in the PR. It was initially presented with those charts included, but finally they where removed of the PR.

The HTML Chart provides a split-pane interface allowing users to write and preview their charts instantly:

  • Source Editor: A built-in code editor for writing HTML/JS/CSS directly within GoldenCheetah.
  • Configuration Editor: A UI to define configuration data as key-value pairs. These pairs are stored and can be accessed by the chart’s JavaScript via window.chart.getChartConfig() to easily configure or tweak the HTML view without hardcoding values in the script.
  • Viewer: A live web engine view that renders the chart and connects to the telemetry bridge.

Configuration and html code are stored along the rest of the tiles of a view in training.

HTML code of the charts could be uploaded to the cloud as well as python charts can, to be shared with other users.

Example Code

Below is the default template code generated for any new HTML chart. It serves as a simple example of how to connect to the bridge, parse the telemetry payload, and update the UI in real-time:

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="qrc:///qtwebchannel/qwebchannel.js"></script> <style>body{font-family:sans-serif;padding:12px;color:#333}</style> </head> <body> <h2>GC WebChannel</h2> <div>Speed: <b id="speed">0.0</b> km/h</div> <div>Power: <b id="power">0</b> W</div> <div>HR: <b id="hr">0</b> bpm</div> <div>Cadence: <b id="cad">0</b> rpm</div> <div>Distance: <b id="dist">0.00</b> km</div> <div>State: <b id="state">Stopped</b></div> <script> if (typeof QWebChannel !== 'undefined') { new QWebChannel(qt.webChannelTransport, function(channel) { window.gc = channel.objects.gc; if (window.gc.telemetry) { window.gc.telemetry.connect(function(payload) { var d = typeof payload === 'string' ? JSON.parse(payload) : payload; document.getElementById("speed").innerText = (d.speed_kmh || 0).toFixed(1); document.getElementById("power").innerText = Math.round(d.power_w || 0); document.getElementById("hr").innerText = Math.round(d.hr_bpm || 0); document.getElementById("cad").innerText = Math.round(d.cadence_rpm || 0); document.getElementById("dist").innerText = (d.distance_km || 0).toFixed(2); }); } if (window.gc.stateChanged) { window.gc.stateChanged.connect(function(state) { document.getElementById("state").innerText = state.toUpperCase(); }); } }); } </script> </body> </html> Tests and feedback from the users

As said, anyone without html knowledge can potentially create html views, using AI, for example (that is may case).
There are no restrictions on creating, for example, a view similar to the one shown in this issue: Gauge view in train mode , referenced in the PR.
Although in that case, we will need to extend the current configuration and telemetry data provided to html: workout text messages defined at specific times, target power of the current lap, duration of the workout and its full definition, etc; things that will be added in next deliveries if there is interest, I guess.
This functionality is not closed with this PR.

Therefore, I’d appreciate any feedback. Binaries will be available soon, Ale is launching a build.

I am attaching the two html charts that I am showing in the video.
Please note that they are not final versions nor official files. They were created to test behavior of the code during development. In fact, unfortunately, the code for the map is mostly in spanish (sorry for that), as it was a test.
In the header of the map code, there is a description of the current variables that can be used to model the behavior of the page, in the configuration section of the chart.

I am also adding them to the tests charts of the project (test/charts path of the project).

HTMLChartTraining

TrainDashboard.gchart
TrainMap.gchart

Ale Martinez

unread,
Jun 18, 2026, 4:44:37 PM (9 days ago) Jun 18
to golden-cheetah-users
Thank you, Miguel, great work!

The PR is merged and included in latest Snapshot builds: https://github.com/GoldenCheetah/GoldenCheetah/releases/tag/snapshot 

Cheers, Ale.

Joachim Kohlhammer

unread,
Jun 20, 2026, 3:37:59 AM (7 days ago) Jun 20
to golden-cheetah-users
Great addition! I just started creating a widget with Claude showing the current power zone. Styling is still off (not responsive when resizing horizontally, only dark mode) - but already quite close to what I always wanted.Bildschirmfoto_20260620_092108.png
Is it possible to extend the available data? For my usecase it would be helpful to have
  • target power in the live stream
  • zone names and colors in config, similar to lower bounds that are already available
  • configured font in config (not too sure about this one)
I didn't test the feature with a real indoor ride yet (robot only, weather is too good not to ride outdoors) but couldn't find any issue. It seems just to work as expected.

Cheers, Joachim

Peret

unread,
Jun 20, 2026, 8:21:55 AM (7 days ago) Jun 20
to golden-cheetah-users
Hello, Joachim, thanks for testing. I am happy to know that it is being used.
Sure, I am in the process of adding some data. I have noticed that there is a huge amount of telemetry prosibilities, so, if you agree, I would prioritize some data; I prefer a more that on PRs (basic and most common data and then the 'rare' data, or not likely to be used at the begining), to have results (I hope) in a short time.
My short term tasks are to document and to start adding most telemetry/config data. Perhaps it is more useful to add some important data and then documenting with more data?

OTOH, I am appreciate to receive some priorities for the data, as you are listing (a good starting point). I am a very basic user, so I am not sure of the relevance of some data.
Regards

Joachim Kohlhammer

unread,
Jun 20, 2026, 1:09:07 PM (7 days ago) Jun 20
to golden-cheetah-users
Fully agree, lets not add everything and the kitchen sink before we know what really is useful and required.
Attached you find my current power widget with zone display. The zone names are currently hardcoded, lows taken from config. The shortnames (Z1..Z10) are built using the index of the current zone. This is why I asked for the additional data (including colors for the zones).
I would like to add the target power (I am an erg mode guy) and W' (as a bar right next to the existing one).
It would also be handy if the config contained a flag for dark or light mode to style the widget according to the rest (dark = GCColor::isDark(GColor(CPLOTBACKGROUND));)

Please be aware that this widget is 100% AI generated (with many iterations).

What I haven't tested yet is if this puts additional load on my trusty old Workout-Notebook (2011 MacBook running Linux). Will do so probably tomorrow. But absolutely no issues found yet!

Cheers, Joachim

train-power.html

Joachim Kohlhammer

unread,
Jun 20, 2026, 2:44:29 PM (7 days ago) Jun 20
to golden-cheetah-users
I asked Claude to build a first version of a skill based on my previous attempts. If no one opposes, we could keep this updated along the documentation to support easy creation of widgets for everybody. ChatGPT could import the skill directly.
goldencheetah-widget.skill

Peret

unread,
Jun 20, 2026, 3:46:16 PM (7 days ago) Jun 20
to golden-cheetah-users
It is a very good idea. It seems very complete. Skills or any kind of instructions are subject to 'imagination', so this can grow or be adapted to specific needs, but having a starting point saves time and helps people use it. If you agree, I can add it to the documentation, I will do my best to first add some high priority data to the code before documenting. If you feel like it, you can start the documentation, but I think it is my duty ;)

Joachim Kohlhammer

unread,
Jun 21, 2026, 2:30:28 AM (6 days ago) Jun 21
to golden-cheetah-users
As I was already in, I asked Claude to derive some documentation from the skill and made it validate against HtmlTrainingBridge.cpp. This resulted in the attached md and updated skill. You can use it as a starting point. Please review and feel free to update.
goldencheetah-train-widgets.skill
GoldenCheetah-Train-Widgets-wiki.md

Peret

unread,
Jun 21, 2026, 6:03:38 AM (6 days ago) Jun 21
to golden-cheetah-users

lo, Joachim, thanks for those files! I will have a look at them and will use them for my next html for sure.

In the meanwhile, this is the status of the additional data:

  1. Target Power: OK (erg file selected event)
  2. Zone names (and descriptions) and colors in config: OK (configuration data)
  3. Configured font in config: I do not have a clear idea of this, too. Is there any configuration that can be fetched? Perhaps you mean specific configuration for the chart? In such case, you can add configuration variables at chart level using the interface
  4. Dark/Light mode: you say GCColor::isDark(GColor(CPLOTBACKGROUND)); and also there is TrainBottom::isDark(), hardcoded to GColor(CCHROME). What do you think is better? You coded both :)
  5. sec_msecs_remaining: current section milisecs remaining (erg file selected event)
  6. erg_msecs_remaining: workout milisecs remaining (erg file selected event)
  7. Workout section text: in progress (I am trying to fetch it and show on telemetry updates), with no success for the moment, but it cannot be difficult.

sec_msecs_remaining and erg_msecs_remaining have been fetched empirically. I do not understand very well tha naming of accessors/funtions/variables that keep track of them, although they behave as my purpose, the naming does not point to that… (this is open to your more experienced knowledge, and can be changed):

qint64 sec_msecs_remaining = rt.value(RealtimeData::DataSeries::ErgTimeRemaining); qint64 erg_msecs_remaining = rt.value(RealtimeData::DataSeries::LapTimeRemaining);

Except target_power_w, sec_msecs_remaining and erg_msecs_remaining that have been tested with ERG workouts and seem ok for me, those additional data have not yet (only coded).

Screenshot.pngToday it is difficult to progress on this, but I will see.

Regarding performance I do not think it is an issue, in general terms, for the computer. The webchannel is like functions that are invoked and only get data, and events to synchronize calls; html could be hard to run, but if you are conscious of it it is not likely to happen; the first versions of the real time map I put as example needed more time to render than the telemetry events period, so it behaved wrongly: it was re loading all the map tiles, re building the elevations profile, etc. The AI was told to improve it and it did the magic, only updating the trace instead of replotting all, and since then it works smoothy without performance issues in an old laptop (Linux). But, as you say, we must be arare it has been done by AI without any responsability on my side :)

Joachim Kohlhammer

unread,
Jun 21, 2026, 9:46:41 AM (6 days ago) Jun 21
to golden-cheetah-users
Peret schrieb am Sonntag, 21. Juni 2026 um 12:03:38 UTC+2:

lo, Joachim, thanks for those files! I will have a look at them and will use them for my next html for sure.

You are very welcome !

  1. Configured font in config: I do not have a clear idea of this, too. Is there any configuration that can be fetched? Perhaps you mean specific configuration for the chart? In such case, you can add configuration variables at chart level using the interface
You could try reading the font like in the settings pages: QString telemetryFontFamily = appsettings->value(nullptr, TRAIN_TELEMETRY_FONT_FAMILY, "").toString();
However I am unsure if the string returned will work correctly in a webview via CSS. But lets try...

 
  1. Dark/Light mode: you say GCColor::isDark(GColor(CPLOTBACKGROUND)); and also there is TrainBottom::isDark(), hardcoded to GColor(CCHROME). What do you think is better? You coded both :)
Go for CPLOTBACKGROUND. CCHROME is the background behind the training mode control buttons 
  1. sec_msecs_remaining: current section milisecs remaining (erg file selected event)
  2. erg_msecs_remaining: workout milisecs remaining (erg file selected event)
  3. Workout section text: in progress (I am trying to fetch it and show on telemetry updates), with no success for the moment, but it cannot be difficult.

sec_msecs_remaining and erg_msecs_remaining have been fetched empirically. I do not understand very well tha naming of accessors/funtions/variables that keep track of them, although they behave as my purpose, the naming does not point to that… (this is open to your more experienced knowledge, and can be changed):

qint64 sec_msecs_remaining = rt.value(RealtimeData::DataSeries::ErgTimeRemaining); qint64 erg_msecs_remaining = rt.value(RealtimeData::DataSeries::LapTimeRemaining);

I feel your pain, it took quite some time for me to come to my current setup of dials (Abschnitt corresponds probably to your Next):
Bildschirmfoto_20260621_153601-1.png
Today it is difficult to progress on this, but I will see.

No rush, I don't see any reason for a hurry. As soon as you have a PR I can test it.

Cheers, Joachim

Ale Martinez

unread,
Jun 21, 2026, 10:32:24 AM (6 days ago) Jun 21
to golden-cheetah-users
 ErgTimeRemaining is called Section Time Remaining in Dial config, it applies only to ERG workouts and refers to the time remaining until the current section in the ERG ends, LapTimeRemaining applies to any workout with lap markers and it is the time remaining until the next one.

Ale Martinez

unread,
Jun 21, 2026, 12:27:55 PM (6 days ago) Jun 21
to golden-cheetah-users
El domingo, 21 de junio de 2026 a la(s) 10:46:41 a.m. UTC-3, tiefgara...@gmail.com escribió:
Agree, I think this is an interesting feature and lets try to make it the most feature complete and self contained as possible, we can continue with every 2 month dev releases and more frequent snapshot builds for a while. 

Peret

unread,
Jun 22, 2026, 12:29:07 PM (5 days ago) Jun 22
to golden-cheetah-users
Thanks! All this is solved.

Joachim Kohlhammer

unread,
Jun 23, 2026, 12:33:44 PM (4 days ago) Jun 23
to golden-cheetah-users
Based on your new PR #4889 I updated my widget to react on dark / light, added target power and W', works like a charm:
Bildschirmfoto_20260623_181707.png

Peret

unread,
Jun 24, 2026, 11:26:17 AM (3 days ago) Jun 24
to golden-cheetah-users
Great!
https://github.com/GoldenCheetah/GoldenCheetah/releases/tag/snapshot holds the last commits that allow more data, specially for ERG workouts.
So, you can test it. This a another chart that can be used for ERG and Slope Workouts:

Slope.pngERG.png

HTML Chart.gchart
Reply all
Reply to author
Forward
0 new messages