UG_Special Topics_Working with Python - GoldenCheetah/GoldenCheetah GitHub Wiki

Contents

Below you will find details of each of the GC functions available to use within the Python chart.

Basics

  • GC.version() to get a version string
  • GC.build() to get a build number
  • GC.webpage(filename) to set the webpage

Athlete

  • GC.athlete() to get the athlete details
  • GC.athleteZones(date=0, sport="") to get zone config

Activity

  • GC.activities(filter="") to get a list of activities (as dates)
  • GC.activity(join="repeat", activity=None, compareindex=-1) to get the activity data
  • GC.series(type, activity=None, compareindex=-1) to get an individual series data
  • GC.activityWbal(activity=None, compareindex=-1) to get wbal series data
  • GC.xdataNames(name="", activity=None, compareindex=-1) to get activity xdata series names
  • GC.xdataSeries(name, series, activity=None, compareindex=-1) to get activity xdata series in its own sampling interval
  • GC.xdata(name, series, join="repeat", activity=None, compareindex=-1) to get interpolated activity xdata series
  • GC.activityMetrics(compare=False) to get the activity metrics and metadata
  • GC.activityMeanmax(compare=False) to get mean maximals for all activity data
  • GC.activityIntervals(type="", activity=None) to get information about activity intervals

Trends

  • GC.season(all=False, compare=False) to get season details
  • GC.seasonMetrics(all=False, filter="", compare=False) to get season metrics
  • GC.seasonMeanmax(all=False, filter="", compare=False) to get best mean maximals for a season
  • GC.seasonPeaks(all=False, filter="", compare=False, series, duration) to get activity peaks for a given series and duration
  • GC.seasonPmc(all=False, metric="TSS") to get PMC data for any given metric
  • GC.seasonIntervals(type="", compare=False) to get metrics for all intervals
  • GC.seasonMeasures(all=False, group="Body") to get Daily measures (Body and Hrv available for v3.5)

Charts

  • GC.setChart(...) to setup chart basics
  • GC.addCurve(...) to add a curve to the chart
  • GC.setAxis(...) to control axis settings
  • GC.annotate(...) to add an annotation to the chart
  • An interactive chart example with Code

Working with Python

New in v3.6: GoldenCheetah v3.6 builds include Python 3.7, with required and recommended modules pre-installed, to allow easier access to curated Python charts on CloudDB for non-technical users. To use it check Enable Python, leave Python Home empty in options and restart GC, save and restart GC. For advanced users, willing to install additional packages, a separate Python installation is recommended, as documented below.

Python embedding is the most powerful and flexible way to extend GoldenCheetah, but that flexibility comes with a price; to use it you need to know or be willing to learn about Python. Even to use Python charts developed by others, the minimum is to be able to install Python, configure for embedding in GoldenCheetah and manage Python packages using the standard Python package manager (PIP) at the command line in your OS, some script debugging abilities may be necessary. There are lots of freely available resources to educate yourself on these topics, please use them.

Simpler alternatives are available, s.t. in decreasing order of complexity and flexibility: R Charts, User Charts, Metrics Trends Charts and User Data/Custom Metrics with formulas, and configurable standard charts.

python chart screenshot

Using the Python Chart

Python chart is introduced with v3.5 of GoldenCheetah and it can be used in activity view and trend view.

We build binaries using Python v3.x and they are only compatible with this release, we do not support embedded use of Python 2.7. Since Python is embedded the runtime must match the versions supported by each release of GoldenCheetah, currently at least major and minor Python version (s.t. 3.6) must match the one reported in Help > About > Versions, if you are experiencing problems we recommend to install exactly the same version informed there.

If Python is not installed in (OS dependent) standard locations or present in PATH, the location needs to be configured in Preferences before to enable Python integration. Python venv/virtualenv are not supported.

The path you need to configure Python in GoldenCheetah can be obtained running the Python interpreter and executing the following commands:

import sys
sys.exec_prefix

After you configure Python in GoldenCheetah, restart GoldenCheetah and check Python is recognized in Help > About > Versions.

Note for v3.6 on Windows: to use your own Python installation with GoldenCheetah v3.6, instead of the included one, it is necessary to remove/rename python37._pth file parallel to GoldenCheetah.exe, this file is part of Python embeddable distribution and blocks access to other Python installations when present.

Recommended Modules and use of pip

We recommend using `pip' to install and manage python modules and expect the pip module to have been installed. We highly recommend installing this following modules since they are assumed to be available when developing charts:

GoldenCheetah Integration (required)

  • sip (must be version 4.19.x, e.g. pip install sip==4.19.8)

Data processing

  • numpy
  • pandas (recommended version 1.2.3, e.g. pip install pandas==1.2.3)

Data analysis, modelling and learning

  • scipy
  • scikit-learn
  • tensorflow
  • lmfit

Visualisation

  • colour
  • plotly

How the python chart works

In trend view the chart is refreshed when a date range or season is selected, or if the compare pane is updated. Whilst in the activity view the chart is refreshed when an activity is selected or, again, if the compare pane for intervals is updated.

When the chart is refreshed the user supplied Python script is executed, and it can collect data using calls via a python API provided by GoldenCheetah (GC), prepare a plot and then render it as a web page.

Since there is no standard graphics system available in python we have chosen to provide two options, which are configuralble in the chart settings; firstly, you can render via the native charting system or secondly via a web browser. So the python script should use APIs listed below for working with the local graphics system or generate some form of html output that can be loaded by the browser. Since there are a fair number of python modules for plotting and visualisation via a browser, we have chosen to standardise on plotly since it is so popular and capable.

Plotly is built using d3.js and stack.gl. The web library plotly.js is a high-level, declarative charting library. plotly.js ships with 20 chart types, including 3D charts, statistical graphs, and SVG maps all of which can be rendered in the python chart's webview.

There are a good number of alternatives to plotly you might consider using such as bokeh, pygal and ggplot. We recommend using plotly wherever possible to avoid additional dependencies if you choose to share your chart with other users via the cloudDB.

A simple example to plot a cadence and power scatter plot:

##
## Python program will run on selection.
##
import numpy as np
import plotly
from plotly.graph_objs import Scatter, Layout
import tempfile
import pathlib


## Retrieve power and cadence
xx = np.asarray(GC.series(GC.SERIES_WATTS))
yy = np.asarray(GC.series(GC.SERIES_CAD))

# Define temporary file
temp_file = tempfile.NamedTemporaryFile(mode="w+t", prefix="GC_", suffix=".html", delete=False)

## Prepare Plot
plotly.offline.plot({
    "data": [Scatter(x=xx, y=yy, mode = 'markers')],
    "layout": Layout(title="Power / Cadence")
}, auto_open = False, filename=temp_file.name)

## Load Plot
GC.webpage(pathlib.Path(temp_file.name).as_uri())

This basically calls a GC function to fetch the data for the current ride and then puts the data into numpy arrays so that plotly can prepare a plot, before rendering it on the chart webpage.

For details on rendering via the local graphics system see the section at the bottom of this page entitled 'Charts'

Local Data, $$ and avoiding data refresh

All data objects are shared across charts. If you create a vector 'x' in one chart it will be available in all the others. Usually, this is not a problem since you fetch the data manipulate it and plot it. But in some cases, where perhaps you prepare data from across the ride data you may want to avoid refreshing it to save time.

If you prefix variable names with $$ then they will get a prefix added to them internally that makes them local to the chart. e.g. $$d internally becomes 'gc0d' on the first chart, 'gc1d' on the second and so on.

We recommend you always prefix variables with $$ to ensure there is not conflict across charts.

Python globals() function is useful to check if a variable was previously set, for example in the following fragment:

if not $$d in globals():
    $$d = some_slow_computation()
print($$d)

the function some_slow_computation() runs only on the fist chart refresh.

Compare Mode

Where applicable the data access routines will offer a 'compare' parameter to access data; if False it just returns the currently selected ride, if True it will return a list. If compare mode is not active then the list will contain only one set of data for the currently selected ride. If compare is active it returns a list of all things being compared. See GC.activity(compare=True) below for an example.

Search/Filtering

If a search or data filter is active then this will be applied to the results returned by the API methods described below. Even when all=True is set to override season date ranges the filter will be applied. Ultimately, if the user doesn't want a filter to be applied they should clear it. We work this way to ensure consistency across all charts.

Stopping long running scripts

Hitting the ESC key will terminate any long running scripts. This can be useful when processing large volumes of data or an errant script runs into an infinite loop. In general, we would recommend letting long running processes complete, but ESC will terminate them safely.

Copy from the console

Normal selection is disabled in the console output for technical reasons, but you can use Ctrl+A to select and Ctrl-Insert to copy the console contents.

Line breaks in string constants

If you need to use a newline character in a string constant, please use chr(10) since ‘\n’ will break the script after restart.

Keywords to avoid

There are no limitations on the python modules or language features that you can use. But you should avoid using commands that require an interactive shell (since there isn't one), so help, copyright etc will hang the interpreter and GoldenCheetah. You should avoid using quit() (or equivalents) since they will terminate GoldenCheetah immediately and may lead to data loss.

Avoiding blocking

Any blocking action, s.t. waiting for user input or starting a synchronous server, will block GoldenCheetah user interface and it should be avoided, the chart script should terminate as soon as possible. Python treading module can be used for that purpose if necessary.

IPython and Jupyter

In the current release we do not support use of the IPython package. Importing IPython and running embed() in a Python chart console (or script) will hang GoldenCheetah. We will be supporting Jupyter Notebooks in releases after version 3.6 of GoldenCheetah and are looking at this actively.

Using IDE for Python chart development

IDEs are not supported inside GC, but there is an independent development enabling creation and debugging of GC Python chart using Python IDEs, see https://github.com/rb83421/GoldenCheetah_Python_Chart_Wrapper

Basics

All methods to access and work with GC objects and data within a Python chart are prefixed with "GC." this is to ensure there is no namespace clash with other packages you may have installed. There are a number of methods available as listed below.

GC.build() and GC.version()

GC.build will return the build number which increments with each release (including pre-release development builds), you can use this if you need to make a script dependant on the build. Additionally, GC.version will return a string describing the release,which could be put in files or on screen etc.

GC.webpage(filename)

The web browser loads and displays the file filename, it is recommended that this is an html file to ensure cross-platform compatibility but that is not mandated.

Athlete

GC.athlete()

GC.athlete() will return a dict of athlete details including name, home directory, gender, height, weight and date of birth.

e.g.

>>> print(GC.athlete())
{'height': 1.709, 'weight': 67.0, 'gender': 'male', 'dob': datetime.date(1962, 11, 11), 'name': 'Ale', 'home': '/home/ale/.goldencheetah/Ale'}

GC.athleteZones(date=0, sport="")

Will return a dict of the zone configuration by sport in chronological order. The pace, heartrate and power settings are folded together by sport.

You can specify a date you want to get configuration for, or limit to zone config for a specific sport.

Introduced in v3.6: hrzoneslow and pacezoneslow, similar to zoneslow but for HR and Pace, respectively.

e.g.

>>> print(GC.athleteZones().keys())
dict_keys(['pmax', 'cp', 'date', 'ftp', 'zonescolor', 'zoneslow', 'cv', 'hrmax', 'rhr', 'sport', 'wprime', 'lthr'])

where zonescolor and zoneslow are lists with an element for each Zone.

If you prefer to use Pandas the dict can be easily converted to a Data Frame:

e.g.

>>> import pandas as pd
>>> df = pd.DataFrame(GC.athleteZones())
>>> print(df)
       cp    cv        date    ftp  hrmax   lthr    pmax   rhr sport   wprime  \
0     0.0   3.0  2000-01-01    0.0    0.0    0.0     0.0   0.0  swim      0.0   
1     0.0  15.0  2000-01-01    0.0  176.0  158.0     0.0  50.0   run      0.0   
2   240.0   0.0  2000-01-01  240.0  170.0  150.0  1000.0  50.0  bike  20000.0   
3   170.0   0.0  2012-10-01  170.0  170.0  150.0  1000.0  50.0  bike  20000.0   
4   180.0   0.0  2012-10-29  180.0  170.0  150.0  1000.0  50.0  bike  20000.0   
5   190.0   0.0  2012-11-26  190.0  170.0  150.0  1000.0  50.0  bike  20000.0   
6   200.0   0.0  2012-12-24  200.0  170.0  150.0  1000.0  50.0  bike  20000.0   
7   210.0   0.0  2013-01-21  210.0  170.0  150.0  1000.0  50.0  bike  20000.0   
8   220.0   0.0  2013-02-18  220.0  170.0  150.0  1000.0  50.0  bike  20000.0   
9   230.0   0.0  2013-03-18  230.0  170.0  150.0  1000.0  50.0  bike  20000.0   
10  240.0   0.0  2013-04-15  240.0  170.0  150.0  1000.0  50.0  bike  20000.0   
11  230.0   0.0  2013-07-15  230.0  170.0  150.0  1000.0  50.0  bike  20000.0   
12  220.0   0.0  2013-08-12  220.0  170.0  150.0  1000.0  50.0  bike  29000.0   
13    0.0  13.0  2013-12-30    0.0  176.0  158.0     0.0  50.0   run      0.0   
14    0.0  13.0  2014-01-27    0.0  176.0  158.0     0.0  50.0   run      0.0   
15  230.0   0.0  2014-02-03  230.0  170.0  150.0  1000.0  50.0  bike  20000.0   
16    0.0  14.0  2014-03-24    0.0  176.0  158.0     0.0  50.0   run      0.0   
17    0.0  13.0  2014-04-24    0.0  176.0  158.0     0.0  50.0   run      0.0   
18    0.0  14.0  2014-05-19    0.0  176.0  158.0     0.0  50.0   run      0.0   
19    0.0  14.0  2014-06-16    0.0  176.0  158.0     0.0  50.0   run      0.0   
20    0.0  15.0  2014-07-14    0.0  176.0  158.0     0.0  50.0   run      0.0   
21  240.0   0.0  2014-07-14  240.0  170.0  150.0  1000.0  50.0  bike  20000.0   
22    0.0  15.0  2014-08-11    0.0  176.0  158.0     0.0  50.0   run      0.0   
23  250.0   0.0  2014-09-29  250.0  170.0  150.0  1000.0  50.0  bike  20000.0   
24  240.0   0.0  2014-11-17  240.0  170.0  150.0  1000.0  50.0  bike  20000.0   
25  230.0   0.0  2014-12-28  230.0  170.0  150.0  1000.0  50.0  bike  20000.0   
26    0.0   3.0  2015-01-01    0.0    0.0    0.0     0.0   0.0  swim      0.0   
27  225.0   0.0  2015-04-13  225.0  170.0  150.0   800.0  50.0  bike  16000.0   
28    0.0  13.0  2015-04-13    0.0  176.0  158.0     0.0  50.0   run      0.0   
29    0.0  14.0  2015-05-25    0.0  176.0  158.0     0.0  50.0   run      0.0   
..    ...   ...         ...    ...    ...    ...     ...   ...   ...      ...   
33    0.0   3.0  2015-07-13    0.0    0.0    0.0     0.0   0.0  swim      0.0   
34    0.0  14.0  2015-10-12    0.0  176.0  158.0     0.0  50.0   run      0.0   
35    0.0  13.0  2015-12-01    0.0  176.0  158.0     0.0  50.0   run      0.0   
36  250.0  13.0  2016-03-01  250.0  176.0  158.0   650.0  50.0   run  21000.0   
37  250.0  13.0  2016-05-02  230.0  176.0  158.0   600.0  50.0   run  15000.0   
38  220.0   0.0  2016-05-02  220.0  170.0  150.0   874.0  50.0  bike  15461.0   
39    0.0   3.0  2016-05-02    0.0    0.0    0.0     0.0   0.0  swim      0.0   
40  225.0   0.0  2016-06-20  225.0  170.0  150.0   874.0  50.0  bike  15461.0   
41    0.0   3.0  2016-06-20    0.0    0.0    0.0     0.0   0.0  swim      0.0   
42  250.0  14.0  2016-06-20  230.0  176.0  158.0   600.0  50.0   run  15000.0   
43  230.0   0.0  2016-07-18  230.0  170.0  150.0   874.0  50.0  bike  15461.0   
44  250.0  14.0  2016-07-18  230.0  176.0  158.0   600.0  50.0   run  15000.0   
45    0.0   3.0  2016-07-18    0.0    0.0    0.0     0.0   0.0  swim      0.0   
46    0.0   3.0  2016-08-15    0.0    0.0    0.0     0.0   0.0  swim      0.0   
47  235.0   0.0  2016-08-15  235.0  170.0  150.0   874.0  50.0  bike  15461.0   
48  250.0  14.0  2016-08-15  230.0  176.0  158.0   600.0  50.0   run  15000.0   
49  220.0   0.0  2016-12-05  220.0  170.0  150.0   800.0  50.0  bike  15000.0   
50  250.0  13.0  2016-12-05  230.0  176.0  158.0   600.0  50.0   run  15000.0   
51    0.0   3.0  2017-01-09    0.0    0.0    0.0     0.0   0.0  swim      0.0   
52  250.0  13.0  2017-03-06  230.0  176.0  158.0   600.0  50.0   run  15000.0   
53  225.0   0.0  2017-03-06  225.0  170.0  150.0   800.0  50.0  bike  15000.0   
54    0.0   3.0  2017-03-06    0.0    0.0    0.0     0.0   0.0  swim      0.0   
55  250.0  14.0  2017-04-03  230.0  168.0  144.0   600.0  44.0   run  15000.0   
56  230.0   0.0  2017-04-03  230.0  162.0  139.0   800.0  44.0  bike  15000.0   
57    0.0   3.0  2017-04-03    0.0    0.0    0.0     0.0   0.0  swim      0.0   
58  250.0  15.0  2017-07-03  230.0  168.0  144.0   600.0  44.0   run  15000.0   
59  240.0   0.0  2017-07-03  240.0  162.0  139.0   800.0  44.0  bike  17000.0   
60    0.0   3.0  2017-07-03    0.0    0.0    0.0     0.0   0.0  swim      0.0   
61  200.0   0.0  2017-12-04  200.0  162.0  139.0   800.0  44.0  bike  15000.0   
62    0.0   3.0  2017-12-04    0.0    0.0    0.0     0.0   0.0  swim      0.0   

                                           zonescolor  \
0                                                  []   
1                                                  []   
2   [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
3   [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
4   [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
5   [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
6   [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
7   [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
8   [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
9   [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
10  [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
11  [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
12  [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
13                                                 []   
14                                                 []   
15  [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
16                                                 []   
17                                                 []   
18                                                 []   
19                                                 []   
20                                                 []   
21  [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
22                                                 []   
23  [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
24  [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
25  [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
26                                                 []   
27  [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
28                                                 []   
29                                                 []   
..                                                ...   
33                                                 []   
34                                                 []   
35                                                 []   
36  [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
37  [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
38  [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
39                                                 []   
40  [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
41                                                 []   
42  [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
43  [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
44  [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
45                                                 []   
46                                                 []   
47  [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
48  [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
49  [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
50  [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
51                                                 []   
52  [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
53  [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
54                                                 []   
55  [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
56  [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
57                                                 []   
58  [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
59  [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
60                                                 []   
61  [#ff00ff, #2a00ff, #00aaff, #00ff80, #55ff00, ...   
62                                                 []   

                                           zoneslow  
0                                                []  
1                                                []  
2   [0.0, 132.0, 180.0, 216.0, 252.0, 288.0, 360.0]  
3    [0.0, 93.0, 127.0, 153.0, 178.0, 204.0, 255.0]  
4    [0.0, 99.0, 135.0, 162.0, 189.0, 216.0, 270.0]  
5   [0.0, 104.0, 142.0, 171.0, 199.0, 228.0, 285.0]  
6   [0.0, 110.0, 150.0, 180.0, 210.0, 240.0, 300.0]  
7   [0.0, 115.0, 157.0, 189.0, 220.0, 252.0, 315.0]  
8   [0.0, 121.0, 165.0, 198.0, 231.0, 264.0, 330.0]  
9   [0.0, 126.0, 172.0, 207.0, 241.0, 276.0, 345.0]  
10  [0.0, 132.0, 180.0, 216.0, 252.0, 288.0, 360.0]  
11  [0.0, 126.0, 172.0, 207.0, 241.0, 276.0, 345.0]  
12  [0.0, 121.0, 165.0, 198.0, 231.0, 264.0, 330.0]  
13                                               []  
14                                               []  
15  [0.0, 126.0, 172.0, 207.0, 241.0, 276.0, 345.0]  
16                                               []  
17                                               []  
18                                               []  
19                                               []  
20                                               []  
21  [0.0, 132.0, 180.0, 216.0, 252.0, 288.0, 360.0]  
22                                               []  
23  [0.0, 137.0, 187.0, 225.0, 262.0, 300.0, 375.0]  
24  [0.0, 132.0, 180.0, 216.0, 252.0, 288.0, 360.0]  
25  [0.0, 126.0, 172.0, 207.0, 241.0, 276.0, 345.0]  
26                                               []  
27  [0.0, 123.0, 168.0, 202.0, 236.0, 270.0, 337.0]  
28                                               []  
29                                               []  
..                                              ...  
33                                               []  
34                                               []  
35                                               []  
36  [0.0, 200.0, 220.0, 237.0, 262.0, 287.0, 320.0]  
37  [0.0, 200.0, 220.0, 237.0, 262.0, 287.0, 320.0]  
38  [0.0, 121.0, 165.0, 198.0, 231.0, 264.0, 330.0]  
39                                               []  
40  [0.0, 123.0, 168.0, 202.0, 236.0, 270.0, 337.0]  
41                                               []  
42  [0.0, 200.0, 220.0, 237.0, 262.0, 287.0, 320.0]  
43  [0.0, 126.0, 172.0, 207.0, 241.0, 276.0, 345.0]  
44  [0.0, 200.0, 220.0, 237.0, 262.0, 287.0, 320.0]  
45                                               []  
46                                               []  
47  [0.0, 129.0, 176.0, 211.0, 246.0, 282.0, 352.0]  
48  [0.0, 200.0, 220.0, 237.0, 262.0, 287.0, 320.0]  
49  [0.0, 121.0, 165.0, 198.0, 231.0, 264.0, 330.0]  
50  [0.0, 200.0, 220.0, 237.0, 262.0, 287.0, 320.0]  
51                                               []  
52  [0.0, 200.0, 220.0, 237.0, 262.0, 287.0, 320.0]  
53  [0.0, 123.0, 168.0, 202.0, 236.0, 270.0, 337.0]  
54                                               []  
55  [0.0, 200.0, 220.0, 237.0, 262.0, 287.0, 320.0]  
56  [0.0, 126.0, 172.0, 207.0, 241.0, 276.0, 345.0]  
57                                               []  
58  [0.0, 200.0, 220.0, 237.0, 262.0, 287.0, 320.0]  
59  [0.0, 132.0, 180.0, 216.0, 252.0, 288.0, 360.0]  
60                                               []  
61  [0.0, 110.0, 150.0, 180.0, 210.0, 240.0, 300.0]  
62                                               []  

[63 rows x 12 columns]

Activity

In the activity view you will work primarily with activities. The functions listed below return data related to the currently selected activity or are directly related to that context.

GC.activities(filter="")

Will return an array of dates representing the start daytime for each activity, these can be used as the activity parameter for remaining Activity functions to get more information about them.

The filter parameter will be applied as a datafilter to limit the results, enabling you to select specific types of rides. You can pass multiple filters as a string vector.

Here we get a list of activities where the ride intensity was impossible:

>>> bust=GC.activities(filter="IF>1.0 and Time_Recording > 3600")
>>> print(bust)
[datetime.datetime(2007, 4, 13, 0, 0), 
datetime.datetime(2007, 4, 13, 11, 0), 
datetime.datetime(2008, 11, 22, 10, 21, 20), 
datetime.datetime(2008, 12, 1, 13, 5, 19), 
datetime.datetime(2008, 12, 3, 11, 31, 32), 
datetime.datetime(2008, 12, 19, 13, 6, 38), 
datetime.datetime(2008, 12, 21, 14, 49, 35), 
datetime.datetime(2009, 3, 9, 12, 35, 23), 
datetime.datetime(2009, 4, 20, 11, 33, 16)]

GC.activity(join="repeat", activity=None, compareindex=-1)

By default (activity=None) it will return a dict representing the data for the currently selected ride. The keys have been selected to remain compatible with the names in the R API, the values are the same kind of objects returned by GC.series.

The join parameter specifies how the xdata series are interpolated, allowed values are "repeat" (missing values are copied from the previous one), "sparse" (missing values are filled with zeroes) and "interpolate" (missing values are interpolated from adjacent ones).

The activity parameter is the datetime of the specific activity requested, likely obtained from GC.activities().

The compareindex parameter was added in v3.6 and it can be used to obtain data for a compare item when compare mode is enabled, valid compareindex starts from 0 and can reference any of the selected compare items, compatible with the arrays returned by GC.activityMetrics(compare=True) and GC.activityMeanmax(compare=True) functions.

>>> ride = GC.activity()
>>> print(ride)
{'seconds': <goldencheetah.PythonDataSeries object at 0x7f8c59176a68>, 
'cadence': <goldencheetah.PythonDataSeries object at 0x7f8c591769d8>, 
'cadenced': <goldencheetah.PythonDataSeries object at 0x7f8c59176948>, 
'heart.rate': <goldencheetah.PythonDataSeries object at 0x7f8c58aa9438>, 
'heart.rated': <goldencheetah.PythonDataSeries object at 0x7f8c58aa93a8>, 
'distance': <goldencheetah.PythonDataSeries object at 0x7f8c58aa94c8>, 
'speed': <goldencheetah.PythonDataSeries object at 0x7f8c58aa9558>, 
'acceleration': <goldencheetah.PythonDataSeries object at 0x7f8c58aa95e8>, 
'power': <goldencheetah.PythonDataSeries object at 0x7f8c58aa9678>, 
'powerd': <goldencheetah.PythonDataSeries object at 0x7f8c58aa9708>, 
'altitude': <goldencheetah.PythonDataSeries object at 0x7f8c58aa9798>, 
'longitude': <goldencheetah.PythonDataSeries object at 0x7f8c58aa9828>, 
'latitude': <goldencheetah.PythonDataSeries object at 0x7f8c58aa98b8>, 
'slope': <goldencheetah.PythonDataSeries object at 0x7f8c58aa9948>, 
'apower': <goldencheetah.PythonDataSeries object at 0x7f8c58aa99d8>, 
'gearratio': <goldencheetah.PythonDataSeries object at 0x7f8c58aa9a68>}
>>> print(len(ride["seconds"]))
6645

Depending upon the data you have available in the activity you may see more columns, for example pedal based power meters and muscle oxygen devices will return additional data.

GC.series(series, activity=None, compareindex=-1)

If you only want to retrieve a specific data series from the activity. You will need to specify the series you want to retrieve, which can be one of:

  • SERIES_SECS SERIES_CAD SERIES_CADD SERIES_HR SERIES_HRD SERIES_KM
  • SERIES_KPH SERIES_KPHD SERIES_NM SERIES_NMD SERIES_WATTS SERIES_WATTSD
  • SERIES_ALT SERIES_LON SERIES_LAT SERIES_HEADWIND SERIES_SLOPE SERIES_TEMP
  • SERIES_INTERVAL SERIES_NP SERIES_XPOWER SERIES_VAM SERIES_WATTSKG SERIES_LRBALANCE
  • SERIES_LTE SERIES_RTE SERIES_LPS SERIES_RPS SERIES_APOWER SERIES_WPRIME
  • SERIES_ATISS SERIES_ANTISS SERIES_SMO2 SERIES_THB SERIES_RVERT SERIES_RCAD
  • SERIES_RCONTACT SERIES_GEAR SERIES_O2HB SERIES_HHB SERIES_RPCO SERIES_LPPB
  • SERIES_RPPB SERIES_LPPE SERIES_RPPE SERIES_LPPPB SERIES_RPPPB SERIES_LPPPE
  • SERIES_RPPPE SERIES_WBAL SERIES_TCORE SERIES_CLENGTH SERIES_APOWERKG SERIES_INDEX SERIES_HRV
>>> power = GC.series(GC.SERIES_WATTS)
>>> print(len(power))
2477
>>> print(power)
power
>>> print(list(power))
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 1.875, 3.75, 5.625, 7.5, 9.375, 11.25, 13.125, 15.0, 
16.875, 18.75, 20.625, 22.5, 24.375, 26.25, 28.125, 30.0, 33.0, 1.0, 1.0, 0.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
...
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]

GC.activityWbal(activity=None, compareindex=-1)

Because the W'bal data series is always calculated in 1 second intervals it is not returned with the activity data -- it would cause problems when data is recorded in 0.5 seconds from a track PM or 1.26s by an old Powertap.

Instead, we return it a simple array of values, and when using compare mode is in a list in the same way as the other activity data.

GC.xdataNames(name="", activity=None, compareindex=-1)

This allows to retrieve xdata series group names when name="" or the individual series when name identifies an xseries group, for example:

>>> print(GC.xdataNames())
['HRV']

>>> print(GC.xdataNames('HRV'))
['R-R', 'R-R flag']

GC.xdataSeries(name, series, activity=None, compareindex=-1)

This allows to retrieve xdata series specified by its group and series name on its own sampling interval, "secs" and "km" are used to retrieve the time and distance base, for example to retrieve R-R intervals from HRV data:

>>> print(list(GC.xdataSeries('HRV', 'R-R')))
[0.406, 0.405, 0.407, ... , 0.436]

GC.xdata(name, series, join="repeat", activity=None, compareindex=-1)

This allows to retrieve xdata series specified by its group and series name on the same time base as the standard series, the join parameter specifies how the xdata series is interpolated, allowed values are "repeat" (missing values are copied from the previous one), "sparse" (missing values are filled with zeroes) and "interpolate" (missing values are interpolated from adjacent ones). For example to obtain the stroke type in second-by-second time base we can use:

GC.xdata('SWIM', 'TYPE')

GC.activityMetrics(compare=False)

Will retrieve a dict of the metric and metadata fields for the current activity, provided they are defined in Data Field config with a non blank Screen Tab. This also includes the ride color according to the current config. If compare is True it will return a list.

>>> metrics=GC.activityMetrics()
>>> print(metrics)
{'date': datetime.date(2017, 7, 10), 
'time': datetime.time(14, 21, 49), 
'axPower': 139.45054, 
'aPower_Relative_Intensity': 0.69725, 
'aBikeScore': 34.0583, 
'Skiba_aVI': 1.24354, 
'aPower_Response_Index': inf, 
'aNP': 151.13802, 
'aIF': 0.75569, 
'aTSS': 39.29262, 
'aVI': 1.34776, 
'aPower_Efficiency_Factor': inf, 
...
 'Device': 'SRM PC8', 
'Recording_Interval': '', 
'Device_Info': '', 
"W'": '0', 
'Tau': '0', 
'Pmax': '0', 
'CP': '0', 
'color': '#7f7f7f'}
>>> print(metrics["TSS"])
38.84683
>>> 

GC.activityMeanmax(compare=False)

Mean maximal data is returned in a dict, with an array for each data series that has mean max data computed. This is a subset of all the data that can be made available (the cache size is expensive and we don't want to cache data that is never or very, very rarely used).

GC.activityIntervals(type="", activity=None)

Will return a dict for intervals of requested type (all if "") in the indicated activity (currently selected if None) with the following keys:

>>> print(GC.activityIntervals().keys())
dict_keys(['start', 'stop', 'name', 'type', 'color', 'selected',
'axPower', 'aPower_Relative_Intensity', 'aBikeScore', 'Skiba_aVI',
'aPower_Response_Index', 'aIsoPower', 'aIF', 'aBikeStress', 'aVI',
...
'TSS', 'SPL', 'Time_in_Sweet_Spot_Zone'])

The values are arrays with an entry for each interval, for example:

>>> print(GC.activityIntervals()["name"])
['Entire Activity', "Alpe d'Huez", 'Galibier', 'Telegraph', 'Glandon', 'L3 85% EFFORT of 33:49  (215 watts)', 'L4 92% EFFORT of 30:19  (232 watts)', 'L4 85% EFFORT of 25:50  (217 watts)', 'L6 MATCH 01:07 (294w 3 kJ)', 'L6 MATCH 36 (356w 4 kJ)']

>>> print(GC.activityIntervals()["type"])
['ALL', 'USER', 'USER', 'USER', 'USER', 'EFFORTS', 'EFFORTS', 'EFFORTS', 'EFFORTS', 'EFFORTS']

>>> print(GC.activityIntervals()["Duration"])
[40169.0, 8535.0, 7967.0, 4769.0, 6632.0, 2030.0, 1820.0, 1551.0, 67.0, 36.0]

The "selected" array indicates which intervals are selected on the side bar, start/stop are the seconds from the start of the activity delimiting the interval which can be used to access sample data corresponding to the intervals.

New in v3.6 GC.intervalType(type=1) return the localized interval type description to allow language independent filtering of intervals.

For example the following script will plot power vs cadence -similar to the initial example- but only for the samples corresponding to the selected intervals:

import numpy as np
import plotly
from plotly.graph_objs import Scatter, Layout
import tempfile
import pathlib

# Define temporary file
temp_file = tempfile.NamedTemporaryFile(mode="w+t", prefix="GC_", suffix=".html", delete=False)

laps = GC.activityIntervals()

secs = np.asarray(GC.series(GC.SERIES_SECS))
watts = np.asarray(GC.series(GC.SERIES_WATTS))
cad = np.asarray(GC.series(GC.SERIES_CAD))

# cond is a boolean array to mark samples in selected intervals
cond = (secs < 0)
for i in range(len(laps["selected"])):
    if laps["selected"][i]:
       cond |= (secs >= laps["start"][i]) & (secs < laps["stop"][i])

## Prepare Plot
f =plotly.offline.plot({
    "data": [Scatter(x=watts[cond], y=cad[cond], mode = 'markers')],
    "layout": Layout(title="Power / Cadence for Selected Intervals")
}, auto_open = False, filename=temp_file.name)

## Load Plot
GC.webpage(pathlib.Path(temp_file.name).as_uri())

Example

To showcase the possibility with python charts and the use of plotly. This chart shows the average power of every USER interval, the color of the bar represents the color of the power zone. Furthermore a percentage of your CP at the time of the activity, distance and duration is annotated

# This chart plots for every USER interval the average power the athlete has produced
import bisect
import plotly
import plotly.graph_objs as go
import numpy as np
import tempfile
import pathlib

# Get activity (used to get the date)
activity = GC.activityMetrics()

# Get zone information at this actvity date
zone = GC.athleteZones(date=activity["date"], sport="bike")

# Get intervals USER if that one is not find get all intervals
intervals = GC.activityIntervals(type="USER")
type_title = "USER"

# Define tempory file
temp_file = tempfile.NamedTemporaryFile(mode="w+t", prefix="GC_", suffix=".html", delete=False)

# Define GC background color
gc_bg_color = 'rgb(52,52,52)'
# Define GC Text color
gc_text_color = 'rgb(255,255,255)'

if intervals["name"]:
    # Identify for every interval the zone color
    breaks = zone["zoneslow"][0]
    zone_colors = zone["zonescolor"][0]
    interval_colors = []
    avg_power_pct =[]
    for interval in intervals["Average_Power"]:
      id = bisect.bisect_left(breaks, interval)
      interval_colors.append(zone_colors[id-1])
      avg_power_pct.append(str(round((interval / zone["cp"][0])*100,1))+"%")
        
    # Define chart title
    title = "Average Power per Interval (CP:" + str(zone["cp"][0]) + ") Interval Type=" + str(type_title)
    
    # Add percentage labels
    zone_names = ["Z1", "Z2", "Z3", "Z4", "Z5", "Z6", "Z7"]
    legend = []
    zone_index=1
    for zone in breaks:
        legend.append("Z" + str(zone_index) + "(" + str(zone) + ")")
        zone_index += 1

    # array of lap names to printed on the x-axis
    lap_names = np.asarray(intervals["name"])
    # array of y values
    watts_y = np.asarray(intervals["Average_Power"])
    # define x-axis (start time of the intervals)
    x = np.asarray(intervals["start"])

    # arrays used for text for every interval 
    duration = np.asarray(intervals["Duration"])
    distance = np.asarray(intervals["Distance"])
    
    trace0 = go.Scatter(
        x=x,
        y=watts_y,
        mode='text',
        showlegend=False,   
    )
    
    data = [trace0]

    #workaround to get a custom legend
    for i in np.arange(0,len(legend)):
        data.append(go.Scatter(
            x=[None],
            y=[None],
            mode='markers',
            marker=dict(size=10, color=zone_colors[i]),
            legendgroup=legend[i],
            showlegend=True,
            name=legend[i],
            )
        )
       
    # Create rectangles per interval
    shapes = []
    annotations = []
    x_label_pos = []
    
    for i in np.arange(0,len(lap_names)):
        x_label_pos.append(x[i] + (duration[i]/2))
    
        shapes.append(
            {
                'type': 'rect',
                'x0': x[i],
                'y0': 0,
                'x1': x[i]+duration[i],
                'y1': watts_y[i],
                'fillcolor': interval_colors[i],
            })
    
        m, s = divmod(duration[i], 60)
        h, m = divmod(m, 60)
        if h > 0:
            duration_formatted=str(int(h)) + "h" + str(int(m)) + "m" + str(int(s)) + "s"
        elif m > 0:
            duration_formatted = str(int(m)) + "m" + str(int(s)) + "s"
        else:
            duration_formatted = str(int(s)) + "s"   
        annotations.append(
            dict(
                x=x[i] + (duration[i]/2),
                y=watts_y[i],
                xref='x',
                yref='y',
                text=str(avg_power_pct[i]) + "<br>" + duration_formatted + "<br>" + str(round(distance[i], 2)) + "km",
                showarrow=True,
                arrowhead=7,
                arrowcolor=gc_text_color,
                ax=0,
                ay=-40,
                font=dict(
                    color=gc_text_color,
                    size=12
                ),
            )
         )
    # end for 

    layout = go.Layout(
        title=title,
	paper_bgcolor=gc_bg_color, 
        plot_bgcolor=gc_bg_color,
        font=dict(
            color=gc_text_color,
            size=12
        ),
        xaxis=dict(
            tickvals=x_label_pos,
            ticktext=lap_names,
            tickangle=45,
            showgrid=True,
            rangemode='nonnegative',
        ),
        yaxis = dict(
            range=[0,  max(watts_y)+100],
            nticks = int(max(watts_y)/10),
            ticks='outside',
            showgrid=True,
            zeroline=True,
            showline=True,
            gridcolor="grey",
            title="Watts",
        ),
        margin=go.layout.Margin(
            l=100,
            r=0,
            b=100,
            t=150,
            pad=0
        ),
        shapes=shapes,
        annotations=annotations,
    )
    
    fig = go.Figure(data=data, layout=layout)
    plot = plotly.offline.plot(fig, auto_open=False, filename=temp_file.name)   
else:
  f = open(temp_file.name,"w+")
  lines_of_text = [
  "<html>",
  "<body>",
  "<p> Unable to draw plot <br> NO USER Intervals found</p>",
  "</body>",
  "</html>"
  ]
  f.writelines(lines_of_text)
  f.close()

# Load the webpage
GC.webpage(pathlib.Path(temp_file.name).as_uri())

Trends

When plotting in the trends view you are looking at data across a date range or season (as selected in the sidebar). These functions return data in that context.

GC.season(all=False, compare=False)

Will return a dict of seasons, by default it contains one item for the currently selected season but if all is True it will return all seasons configured, and if compare is True will return all seasons being compared. If compare is True and compare is not active it will return the currently selected season.

NOTE: season colors are always set to gray unless in compare mode, in which case they are the compare mode color, to enable use in a plot legend using legend(legend=season$name, fill=season$color ...).

GC.seasonMeanmax(all=False, filter="", compare=False)

As for activity above this will return a dict of mean maximals for data series. If you use all and compare together you will get mean maximals for all activities in a list.

As for GC.activities() you can pass a datafilter to get results for activities that pass a specific rule e.g. 'Data contains "P"' to get results only for rides with power.

For example to plot an all-time power-duration curve of bike activities:

import plotly
from plotly.graph_objs import Scatter, Layout
import tempfile
import pathlib

## Define temporary file
temp_file = tempfile.NamedTemporaryFile(mode="w+t", prefix="GC_", suffix=".html", delete=False)

## Fetch meanmax power for all Bike activities
yy = GC.seasonMeanmax(all=True, filter='Sport="Bike"')["power"]
## Duration in minutes
xx = [s/60 for s in range(1, len(yy)+1)]

## Prepare Plot
f = plotly.offline.plot({
    "data": [Scatter(x=xx, y=yy, mode = 'markers')],
    "layout": Layout(title="Power / Duration")
}, auto_open=False, filename=temp_file.name)

## Load Plot
GC.webpage(pathlib.Path(temp_file.name).as_uri())

GC.seasonMetrics(all=False, filter="", compare=False)

Will fetch the metrics for rides in the currently selected date range, or all dates if all=True. As you would expect the compare parameter will return a list.

As for GC.season.metrics() you can pass a datafilter to get results for activities that pass a specific rule e.g. 'Data contains "P"' to get results only for rides with power.

Of note here are the metadata fields are also returned in the dict, provided they are defined in Data Field config with a non blank Screen Tab, a date field as well as a datetime field and the ride color as applied using the GC coloring rules. Color is a text string in "#rrggbb" format.

It is common to use the color column to control coloring on the plots.

>>> metrics=GC.seasonMetrics()
>>> print(metrics.keys())
dict_keys(['date', 'time', 'color', 'axPower', 'aPower_Relative_Intensity', 
'aBikeScore', 'Skiba_aVI', 'aPower_Response_Index', 'aNP', 'aIF', 'aTSS', 
...
'HRV_Recovery_Points', 'My_Average_Power', 'Corrected_TSS', 'Swim_Stroke_Rate', 
'Interval_Start_Time', 'Start_Date', 'Start_Time', 'Workout_Code', 'Sport', 
'Interesting', 'RPE', 'Weight', 'Route', 'Test', 'Objective', 'Notes', 
'Device', 'Recording_Interval', 'Device_Info', "W'", 'Tau', 'Pmax', 'CP'])
>>> print(len(metrics["TSS"]))
245
>>> print(metrics["TSS"])
[75.21674, 254.26863, 134.64092, 139.27675, 0.0, 94.02194, 105.203, 
158.11475, 0.0, 140.19737, 30.65103, 245.93096, 139.08726, 260.82739, 
101.42868, 101.73081, 220.99914, 125.84831, 111.79441, 95.43517, 
78.12738, 107.35468, 170.90533, 87.42952, 55.27651, 71.00733, 151.6827, 
132.65537, 66.42788, 60.38812, 147.6106, 140.40869, 128.13481, 219.02178, 
...
144.37638, 67.57861, 305.73197, 111.46286, 143.57266, 164.15173, 129.5378, 
108.03523, 125.38076, 165.35482, 59.27895, 62.30294, 459.14827, 98.74112, 
98.66374, 106.68816, 129.87613, 158.80093, 58.05712, 77.20183, 50.31805, 
90.43846, 75.07171, 104.40067, 107.22985, 55.29352, 16.37401, 48.98044, 
115.24527]
>>> 

GC.seasonPeaks(all=False, filter="", compare=False, series, duration)

To get the peaks for all activities in a season, the series should be a name for which mean maximal data is available ("power", "heart.rate", "speed" et al) and the duration should be the duration is seconds.

It will return a dict with arrays for each series+duration combination as well as a datetime array for the datetime the peak was recorded. Bear in mind there will be one peak for every activity, rather than per day.

As for GC.seasonMetrics() you can pass a datafilter to get results for activities that pass a specific rule e.g. 'Data contains "P"' to get results only for rides with power.

As with most of the methods compare=True will return a list for each date range being compared or the current date range if compare is not active.

Example:

power3hr = GC.seasonPeaks(filter='Data contains "P"', series="power", duration=10800)
print(power3hr.keys())
dict_keys(['peak_power_10800', 'datetime'])
import pandas as pd
print(pd.DataFrame(power3hr))
              datetime  peak_power_10800
0  2017-09-12 13:21:16        145.166214
1  2017-09-14 13:28:21        160.845978
2  2017-09-16 11:22:46        142.622757
3  2017-09-19 14:32:33        156.784943
4  2017-09-23 11:23:10        162.980225
5  2017-09-25 14:40:18        151.665512
6  2017-09-28 14:10:39        154.403564
7  2017-09-30 11:23:46        155.030121
8  2017-10-03 14:04:24        157.624023
9  2017-10-05 14:33:50        163.666779
10 2017-10-07 08:58:11        161.556778
11 2017-10-10 15:02:29          0.000000
12 2017-10-13 14:03:44        176.519775
13 2017-10-16 10:32:26        159.312759
14 2017-10-17 14:57:35          0.000000
15 2017-10-19 15:49:59          0.000000
16 2017-10-24 14:27:14        172.231491
17 2017-10-26 14:34:44        158.834366
18 2017-10-28 10:22:29        170.454025
19 2017-10-31 14:41:37        152.802063
20 2017-11-04 10:48:52        159.854950
21 2017-11-07 15:10:45        156.178162
22 2017-11-09 13:52:09        162.017349
23 2017-11-11 12:27:06          0.000000
24 2017-11-13 11:30:08          0.000000
25 2017-11-14 14:33:32          0.000000
26 2017-11-16 14:38:35        152.190689
27 2017-11-18 10:53:00        174.791031
28 2017-11-21 14:33:23          0.000000
29 2017-11-23 10:42:08          0.000000
30 2017-11-26 09:44:28          0.000000
31 2017-11-28 09:21:23          0.000000
32 2017-11-30 09:09:03          0.000000
33 2017-12-02 10:15:27          0.000000
34 2017-12-03 07:51:41        138.638855

GC.seasonPmc(all=False, metric="BikeStress")

To fetch PMC metrics for the currently selected date range, or all dates if all=True, this method returns a dict containing entries for all the PMC metrics series and additionally a date entry.

R and Python PMC functions build on from 0 for the selected date range (or all dates when all=True), which is different from what the Metrics Trends chart does.

You can specify a metric to use (BikeStress is used by default).

Example:

pmc = GC.seasonPmc(metric="TriScore")
print(pmc.keys())
dict_keys(['rr', 'lts', 'date', 'sb', 'stress', 'sts'])
import pandas as pd
print(pd.DataFrame(pmc))
          date        lts         rr         sb     stress        sts
0   2017-09-11   0.000000   0.000000   0.000000    0.00000   0.000000
1   2017-09-12   1.314922   1.314922   0.000000   55.88678   5.318330
2   2017-09-13   1.886642   1.886642  -4.003409   25.61415   7.249733
3   2017-09-14   3.214131   3.214131  -5.363091   58.30758  12.108529
4   2017-09-15   6.177676   6.177676  -8.894398  129.17067  23.248465
5   2017-09-16   8.039930   8.039930 -17.070789   85.32716  29.156034
6   2017-09-17  10.650130  10.650130 -21.116104  118.97860  37.703781
7   2017-09-18  14.088709  14.088709 -27.053652  156.79657  49.036958
8   2017-09-19  15.073101  15.073101 -34.948250   55.92731  49.692662
9   2017-09-20  17.085875  17.085875 -34.619562  100.62000  54.539039
10  2017-09-21  18.875430  18.875430 -37.453164   93.14551  58.212931
11  2017-09-22  19.506150  18.191228 -39.337501   45.68228  57.020481
12  2017-09-23  20.905324  19.018683 -37.514332   78.97385  59.109621
13  2017-09-24  21.596500  18.382369 -38.204296   50.28167  58.269530
14  2017-09-25  21.088371  14.910695 -36.673030    0.00000  52.724451
15  2017-09-26  22.230659  14.190729 -31.636080   69.63788  54.333977
16  2017-09-27  23.879978  13.229849 -32.103318   92.33000  57.949776
17  2017-09-28  23.992463   9.903754 -34.069798   28.66078  55.162560
18  2017-09-29  27.253909  12.180808 -31.170097  162.61040  65.387574
19  2017-09-30  28.414650  11.328775 -38.133665   76.58772  66.453409
20  2017-10-01  28.972920  10.097490 -38.038758   52.14223  65.091520
21  2017-10-02  30.377918  10.871768 -36.118600   88.68810  67.337031
22  2017-10-03  31.127027  10.221703 -36.959114   62.21657  66.849755
23  2017-10-04  31.004105   9.407605 -35.722728   25.90258  62.953116
24  2017-10-05  32.376046  11.287675 -31.949011   89.31434  65.461718
25  2017-10-06  33.114949  10.884290 -33.085672   63.78086  65.301763
26  2017-10-07  34.801433  10.921454 -32.186815  104.79387  69.059934
27  2017-10-08  35.355215  11.362753 -34.258501   58.33829  68.039635
28  2017-10-09  36.913252   9.659344 -32.684420  101.57488  71.230935
29  2017-10-10  37.613652   9.199002 -34.317683   66.68163  70.798012
..         ...        ...        ...        ...        ...        ...
54  2017-11-04  56.774005   4.131537 -20.772884   73.36340  76.787091
55  2017-11-05  59.657339   7.342843 -20.013086  179.32145  86.544525
56  2017-11-06  59.675157   6.461044 -26.887186   60.41464  84.057938
57  2017-11-07  60.168466   6.607520 -24.382780   80.64175  83.732845
58  2017-11-08  59.632148   3.619524 -23.564379   37.37387  79.321205
59  2017-11-09  60.499895   5.805154 -19.689057   96.51315  80.957235
60  2017-11-10  60.698741   5.463008 -20.457339   68.95122  79.814711
61  2017-11-11  59.959642   5.489768 -19.115971   29.28559  75.006230
62  2017-11-12  60.001389   5.216448 -15.046587   61.73395  73.743205
63  2017-11-13  58.589657   2.215378 -13.741817    0.00000  66.725612
64  2017-11-14  59.074468   2.300463  -8.135954   79.19508  67.912238
65  2017-11-15  57.684546  -1.972794  -8.837770    0.00000  61.449534
66  2017-11-16  57.319898  -2.355260  -3.764989   42.18629  59.616394
67  2017-11-17  59.047138  -1.121328  -2.296497  130.73102  66.383846
68  2017-11-18  58.485702  -1.146446  -7.336708   35.18500  63.414883
69  2017-11-19  58.825118  -1.674777  -4.929181   72.91157  64.318612
70  2017-11-20  58.416351  -2.282390  -5.493494   41.45170  62.142538
71  2017-11-21  59.081143  -0.878499  -3.726187   86.67133  64.476761
72  2017-11-22  58.296633  -1.704756  -5.395618   25.73790  60.790271
73  2017-11-23  57.895420  -0.694237  -2.493638   41.24431  58.930227
74  2017-11-24  57.851893  -1.222575  -1.034807   56.04544  58.655703
75  2017-11-25  57.279076  -0.405470  -0.803810   33.50602  56.262394
76  2017-11-26  57.596801   0.276903   1.016682   70.78301  57.644214
77  2017-11-27  58.313836  -0.733301  -0.047413   88.07223  60.539822
78  2017-11-28  58.758142   0.272441  -2.225986   77.19773  62.125032
79  2017-11-29  58.094797  -0.730322  -3.366889   30.56463  59.121662
80  2017-11-30  57.509660  -0.906691  -1.026866   33.22531  56.657299
81  2017-12-01  57.541667  -1.539475   0.852361   58.87006  56.867871
82  2017-12-02  57.085568  -1.211065   0.673797   38.15652  55.087250
83  2017-12-03  57.691655  -0.203766   1.998317   82.84547  57.728794

[84 rows x 6 columns]

GC.seasonIntervals(type="", compare=False)

To fetch details of all intervals across all activities for a season. The response is pretty much the same as you would see for GC.seasonMetrics() with the exception that each row represents an interval and not an activity.

In addition to the metrics each row also contains the interval type and the interval name. The interval type will be one of:

  • ALL - an interval that represents an entire activity
  • DEVICE - an interval specific to the device e.g. calibration points
  • USER - a user defined interval
  • PEAK PACE - a section of an activity that was peak pace for a specific duration
  • PEAK POWER - a section of an activity that was peak power for a specific duration
  • SEGMENTS - a segment found that represents a specific route using GPS
  • CLIMBING - an ascent that was automatically marked
  • EFFORTS - an automatically detected maximal effort

New in v3.6 GC.intervalType(type=1) return the localized interval type description to allow language independent filtering of intervals.

The following example build a table for all the segments in the current date range including selected metrics ordered by name, then duration:

##
## Python program will run on selection.
##
import plotly.graph_objects as go
import pandas as pd
import tempfile, pathlib

# Metrics to be displayed as colums
metric_list = ['name', 'Duration', 'Distance', 'Elevation_Gain', 'Average_Speed', 'Average_Power', 'VAM', 'date']

# Segment list, if empty all are displayed
interval_list = []

# get season intervals as Pandas dataframe
# SEGMENTS are required, but it could be
# USER, EFFORTS, etc.
df = pd.DataFrame(GC.seasonIntervals(type="SEGMENTS"))

# filter selected metrics
if metric_list:
    df = df[metric_list]

# filter selected intervals
if interval_list:
    df = df.loc[df['name'].isin(interval_list)]

# sort by first 2 columns
df = df.sort_values(metric_list[0:2])

fig = go.Figure(data=[go.Table(
    header=dict(values=list(df.columns),
                fill_color='paleturquoise',
                align='left'),
    cells=dict(values=[df[m] for m in metric_list],
               fill_color='lavender',
               align='left'))
])

# Define temporary file
temp_file = tempfile.NamedTemporaryFile(mode="w+t", prefix="GC_", suffix=".html", delete=False)

# Write generated html to temporary file
temp_file.write(fig.to_html())

# Load the html page to GC browser
GC.webpage(pathlib.Path(temp_file.name).as_uri())

GC.seasonMeasures(all=False, group="Body")

To fetch daily measures for the currently selected date range, or all dates if all=True, this method returns a dict containing arrays for all the measures group fields and additionally the dates.

You can specify the measure group to fetch, for v3.5 "Body" and "Hrv" are available.

Example:

hrv = GC.seasonMeasures(group="Hrv")
print(hrv.keys())
dict_keys(['SDNN', 'HF', 'RMSSD', 'AVNN', 'RecoveryPoints', 'date', 'LF', 'PNN50', 'HR'])
import pandas as pd
print(pd.DataFrame(hrv))
       AVNN      HF     HR      LF  PNN50  RMSSD  RecoveryPoints    SDNN  \
0   1342.07  0.0207  44.42  0.0461  16.28  36.44            7.46   64.19   
1   1417.23  0.0223  42.66  0.0493  22.50  56.24            8.09   61.91   
2   1386.80  0.0186  45.48  0.0823  31.71  55.89            8.08   77.49   
3   1429.29  0.0310  41.22  0.0521  22.50  41.47            7.64   88.72   
4   1377.40  0.0363  43.94  0.0603  36.59  97.09            8.96   86.16   
5   1392.22  0.0227  42.20  0.0520  21.95  50.26            7.92   59.44   
6   1440.67  0.0436  41.72  0.0498  32.50  53.93            8.03   64.41   
7   1500.46  0.0350  40.78  0.0822  29.73  57.69            8.13   81.63   
8   1373.67  0.0248  42.14  0.0658  21.43  51.16            7.95   72.18   
9   1322.49  0.0086  45.44  0.0365  18.18  59.93            8.19   71.16   
10  1484.07  0.0283  39.92  0.0772  34.21  75.08            8.54  107.26   
11  1359.97  0.0441  44.52  0.0660  23.81  90.35            8.84   99.41   
12  1463.08  0.0386  41.06  0.0490  25.64  52.38            7.98   77.17   
13  1343.33  0.0377  44.30  0.0817  23.26  70.90            8.45   70.88   
14  1256.91  0.0408  47.62  0.0717  22.22  57.88            8.13   88.01   
15  1271.89  0.0228  46.78  0.0283   6.52  24.73            6.97   45.47   
16  1389.04  0.0392  41.66  0.0456  19.51  42.66            7.68   56.92   
17  1467.53  0.0825  42.02  0.1160  28.21  72.51            8.48   77.60   
18  1349.64  0.0240  42.00  0.0562  23.26  70.52            8.44   73.24   
19  1349.64  0.0240  42.00  0.0562  23.26  70.52            8.44   73.24   
20  1278.40  0.0403  46.58  0.0558  20.00  70.64            8.44   90.94   
21  1310.02  0.0155  45.59  0.0350   6.82  29.56            7.19   35.46   
22  1352.48  0.0396  45.41  0.0795  23.81  44.30            7.74   75.58   
23  1374.14  0.0325  44.11  0.0844  32.50  53.36            8.01   74.95   
24  1375.30  0.0423  44.36  0.0535  23.08  67.17            8.36   93.06   
25  1410.87  0.0200  42.25  0.0660  24.39  51.68            7.96   60.66   
26  1332.70  0.0162  47.00  0.0403  31.71  81.35            8.67   97.99   
27  1364.11  0.0289  44.28  0.0726  30.95  45.53            7.78   85.69   
28  1330.31  0.0589  45.86  0.1292  29.27  74.89            8.53  122.17   
29  1335.76  0.0326  45.02  0.0571  13.95  52.59            7.99   64.23   
..      ...     ...    ...     ...    ...    ...             ...     ...   
54  1378.75  0.0344  44.73  0.0619  25.00  51.18            7.95   60.93   
55  1261.63  0.1593  49.66  0.0995  27.27  94.13            8.91  104.79   
56  1326.07  0.0354  45.33  0.0507  15.91  40.60            7.61   65.30   
57  1317.92  0.0269  45.36  0.0497   6.82  32.91            7.33   53.12   
58  1299.03  0.0216  45.91  0.0400   4.55  30.62            7.23   57.20   
59  1382.96  0.0177  42.77  0.0353  17.07  58.19            8.14   73.53   
60  1414.70  0.0318  41.58  0.0293  21.05  71.46            8.46   68.33   
61  1365.14  0.0169  44.67  0.0310  16.67  34.09            7.38   59.69   
62  1365.14  0.0169  44.67  0.0310  16.67  34.09            7.38   59.69   
63  1365.14  0.0169  44.67  0.0310  16.67  34.09            7.38   59.69   
64  1325.40  0.0357  45.34  0.0370  11.63  86.02            8.76   59.69   
65  1323.60  0.0258  45.16  0.0389  12.50  34.73            7.40   51.33   
66  1451.97  0.0391  41.41  0.0325  15.38  59.89            8.19   60.39   
67  1325.22  0.0206  45.05  0.0389  16.67  86.55            8.77   76.67   
68  1394.47  0.0302  43.09  0.0388  23.08  49.26            7.89   60.21   
69  1302.37  0.0158  42.95  0.0355  13.64  47.25            7.83   55.02   
70  1335.86  0.0321  44.59  0.0342   7.69  37.40            7.50   53.53   
71  1444.61  0.0454  40.58  0.0567  17.95  42.73            7.69   77.22   
72  1357.30  0.0243  44.28  0.0519  21.95  77.66            8.59   59.88   
73  1422.94  0.0424  42.05  0.0475  25.64  85.69            8.75   81.64   
74  1302.99  0.0301  45.69  0.0454  20.45  44.90            7.76   57.40   
75  1302.99  0.0301  45.69  0.0454  20.45  44.90            7.76   57.40   
76  1302.99  0.0301  45.69  0.0454  20.45  44.90            7.76   57.40   
77  1287.94  0.0452  45.83  0.0659  17.78  59.34            8.17   83.05   
78  1277.92  0.0334  46.81  0.0326  24.44  46.28            7.80   93.30   
79  1277.92  0.0334  46.81  0.0326  24.44  46.28            7.80   93.30   
80  1277.92  0.0334  46.81  0.0326  24.44  46.28            7.80   93.30   
81  1267.40  0.0251  48.17  0.0279   6.52  38.90            7.56   58.27   
82  1225.91  0.0615  49.09  0.0528  13.04  65.71            8.33   82.20   
83  1225.91  0.0615  49.09  0.0528  13.04  65.71            8.33   82.20   

          date  
0   2017-09-11  
1   2017-09-12  
2   2017-09-13  
3   2017-09-14  
4   2017-09-15  
5   2017-09-16  
6   2017-09-17  
7   2017-09-18  
8   2017-09-19  
9   2017-09-20  
10  2017-09-21  
11  2017-09-22  
12  2017-09-23  
13  2017-09-24  
14  2017-09-25  
15  2017-09-26  
16  2017-09-27  
17  2017-09-28  
18  2017-09-29  
19  2017-09-30  
20  2017-10-01  
21  2017-10-02  
22  2017-10-03  
23  2017-10-04  
24  2017-10-05  
25  2017-10-06  
26  2017-10-07  
27  2017-10-08  
28  2017-10-09  
29  2017-10-10  
..         ...  
54  2017-11-04  
55  2017-11-05  
56  2017-11-06  
57  2017-11-07  
58  2017-11-08  
59  2017-11-09  
60  2017-11-10  
61  2017-11-11  
62  2017-11-12  
63  2017-11-13  
64  2017-11-14  
65  2017-11-15  
66  2017-11-16  
67  2017-11-17  
68  2017-11-18  
69  2017-11-19  
70  2017-11-20  
71  2017-11-21  
72  2017-11-22  
73  2017-11-23  
74  2017-11-24  
75  2017-11-25  
76  2017-11-26  
77  2017-11-27  
78  2017-11-28  
79  2017-11-29  
80  2017-11-30  
81  2017-12-01  
82  2017-12-02  
83  2017-12-03  

[84 rows x 9 columns]

Charts

From version 3.6 onwards you can now plot into an interactive chart withing GoldenCheetah. This allows the user to interact with data points and axes as well as annotate. The overall user experience reflects the rest of GoldenCheetah and provides a more natural way for users to work. We strongly recommend that you develop new charts using this and avoid plotting using the web page.

In general terms, the API for working with charts should be called in sequence;

  • GC.setChart(..) to define the kind of chart and basic settings first
  • GC.addCurve(..) to add data to the chart with implicit axis names
  • GC.annotate(..) to add other kinds of data to the chart
  • GC.setAxis(..) at the end to configure the axes used by the curves

NOTE: Most crucially, you must call setAxis(..) AFTER adding all the curves.

For more information see Working with User Charts since this API mostly allows to access the same functions.

GC.setChart(title, type, animate, legpos, stack, orientation)

title defaults to 1 and it will be displayed at the top of the chart otherwise.

type defaults to 1 and it can be one of:

  • GC.CHART_LINE=1
  • GC.CHART_SCATTER=2
  • GC.CHART_BAR=3
  • GC.CHART_PIE=4
  • GC.CHART_STACK=5
  • GC.CHART_PERCENT=6

animate setting defaults to false and it has been reported to provoke crashes is some setups, if you are experiencing them, please disable animation.

legpos defaults to 2 and it can be one of:

  • GC_ALIGN_BOTTOM=0
  • GC_ALIGN_LEFT=1
  • GC_ALIGN_TOP=2
  • GC_ALIGN_RIGHT=3
  • GC_ALIGN_NONE=4

so legend which can be placed at the top, bottom, left or right of the chart. It can also be set to 'none' which hides the legend from view.

stack defaults to False

orientation defaults to 1 and it can be one of:

  • GC_HORIZONTAL=1
  • GC_VERTICAL=2

The layout direction controls whether separate charts are laid out left to right horizontally or top to bottom vertically. Multiple charts are created when different x-axes are used or series are put into 'groups'.

GC.addCurve(name, x, y, f, xaxis, yaxis, labels, colors, line, symbol, size, color, opacity, opengl, legend, datalabels, fill)

name is mandatory

x and y are the lists of x and y values, it should have the same dimension

f is a list of filenames for click-thru on a trends chart, it should have the same dimension as x and y

xaxis is the x-axis name, it defaults to "x"

yaxis is the y-axis name, it defaults to "y"

labels is an optional list of labels

colors is an optional list of colors

line style defaults to 1, and it can take the following values:

  • GC_LINE_NONE=0
  • GC_LINE_SOLID=1
  • GC_LINE_DASH=2
  • GC_LINE_DOT=3
  • GC_LINE_DASHDOT=4

symbol type defaults to 1, and it can take the following values:

  • GC_SYMBOL_NONE=0
  • GC_SYMBOL_CIRCLE=1
  • GC_SYMBOL_RECTANGLE=2

size is the size/width

color defaults to "cyan"

opacity defaults to 0

opengl defaults to True, it means opengl will be used for rendering. This is very fast and highly recommended for plotting sample by sample data. One of the drawbacks of this is the opengl functions used tend to ignore most of the aesthetic options like transparency and datalabels.

legend defaults to True, and it means this series will be displayed on legend

datalabels defaults to False, and it means data labels will not be displayed

fill defaults to False, and it means the curve will not be filled

GC.setAxis(name, visible, min, max, type, labelcolor, color, log)

name is mandatory and it should match one of the previously added curves axes names.

visible defaults to True

align can be one of:

  • GC_ALIGN_BOTTOM=0
  • GC_ALIGN_LEFT=1
  • GC_ALIGN_TOP=2
  • GC_ALIGN_RIGHT=3
  • GC_ALIGN_NONE=4

min and max default to -1 meaning automatic

type can take one of the following values:

  • GC.AXIS_CONTINUOUS=0 (default)
  • GC.AXIS_DATE=1 for x-axes on Trends charts, dates are represented as the number of days from 1900/01/01
  • GC.AXIS_TIME=2 for x-aces on Activity charts, times are represented as the number for seconds from midnight
  • GC.AXIS_CATEGORY=3 for bar and pie charts

labelcolor and color are optional

log defaults to False, when True a log scale is used, valid only for GC.AXIS_CONTINUOUS type

categories is an optional list of labels for GC.AXIS_CATEGORY type

GC.annotate(type, series, label, value)

For future versions, unimplemented in v3.6

Example Python Chart

A scatter chart, showing power and heartrate compared to cadence, side by side as an interactive chart.

Python Chart Example

The code:

## Retrieve power and cadence
xx = GC.series(GC.SERIES_CAD)


## using qt charts
GC.setChart(title="", 
            type=GC.CHART_SCATTER,
            orientation=GC_HORIZONTAL,
            legpos=GC_ALIGN_TOP,
            stack=True,
            animate=False);

yy = GC.series(GC.SERIES_WATTS)
GC.addCurve(name="Power",x=xx,y=yy,
            size=15, color="orange",
            line=0, opacity=65, opengl=False,
            xaxis="Cadence", yaxis="Watts")


yy = GC.series(GC.SERIES_HR)
GC.addCurve(name="Heartrate",x=xx,y=yy,
            size=15, color="red",
            line=0,opacity=65, opengl=False,
            xaxis="Cadence", yaxis="BPM")

GC.setAxis("Watts",min=1,max=800)
GC.setAxis("Cadence", min=1, max=120)
GC.setAxis("BPM",min=100,max=200)
GC.setAxis("Cad", min=1, max=160)

BACK: Special Topics: Overview
BACK: Table of contents

⚠️ **GitHub.com Fallback** ⚠️