Python Bar Chart

283 views
Skip to first unread message

RB

unread,
Jun 21, 2019, 2:53:36 PM6/21/19
to golden-cheetah-users
Hi all,

I'm experimenting with python chart (plotly is available here so more interactive charts possible then with R charts).


The R chart i want to re-create in python:
#Get activity (used to get the date)
activity
<- GC.activity.metrics()
#Get zone information at this actvity date
zone
<- GC.athlete.zones(date=activity$date, sport="bike")

#Get intervals USER if that one is not find get all intervals
intervals
<- GC.activity.intervals(type="USER")
type_title
<- "USER"
if (is.data.frame(intervals) && nrow(intervals)==0){
  type_title
<- "ALL"
  intervals
<- GC.activity.intervals()
}

#Identify for every interval the zone color
breaks
<- c(unlist(zone$zoneslow))
interval_colors
<- c(unlist(zone$zonescolor))[findInterval(intervals$Average_Power, vec=breaks)]

#Define chart title
title
<- paste0("Average Power per Interval (CP:",zone$cp,") Interval Type=", type_title)

#Define margin (when interval names are not printed propably the names are to long change bottom value(first parameter))
par
(mar=c(10,5,5,0))


barplot
<- barplot(intervals$Average_Power, axes = FALSE, axisnames = FALSE, col=interval_colors, width=intervals$Duration,
main
=title,
ylim
=c(0,max(intervals$Average_Power)*1.1)
)

#Add horizontal line at 0
abline
(h=0)

#Add interval names at X-as
text
(barplot,
     mean
(intervals$Average_Power)*-0.05,
     labels
= intervals$name,
     srt
= 90,
     xpd
= TRUE,
     adj
=0,
)

#Add percentage labels
avg_power_pct
<- round((intervals$Average_Power / zone$cp)*100,1)
avg_power_pct_label
<- paste(avg_power_pct, "%")
text
(barplot,
     intervals$Average_Power
+10,
     labels
= avg_power_pct_label,
)
axis
(2)


#Add legend
zone_names
= c("Z1", "Z2", "Z3", "Z4", "Z5", "Z6", "Z7")
legend
<- paste(zone_names, "(" , breaks, ")")
legend
("topleft",
  inset
=0.02,
  legend
=legend,
  fill
=c(unlist(zone$zonescolor)),
)
R Result:

RChart.png


Python Chart:
import bisect
import plotly
import plotly.graph_objs as go
import numpy as np


#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"
if not intervals["name"]:
  type_title
= "ALL"
  intervals
= GC.activityIntervals()
 

#Identify for every interval the zone color
breaks
= zone["zoneslow"][0]
zone_colors
= zone["zonescolor"][0]
interval_color_list
= []
avg_power_pct
=[]
for interval in intervals["Average_Power"]:
  id
= bisect.bisect_left(breaks, interval)
  interval_color_list
.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"]) + ") 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

xx
= np.asarray(intervals["name"])
yy
= np.asarray(intervals["Average_Power"])
ff
= np.arange(1,len(xx))

#print(ff)
#print(xx)
#print(yy)
data
= []
print(duration)
data
.append(go.Bar(
            x
=xx,
            y
=yy,
            name
="avg power",
#            width=intervals["Duration"],
            text
=avg_power_pct,
            textposition
= 'auto',
            showlegend
=False,
            marker
=dict(
              color
=interval_color_list),
       
))

#workaround to add legend.
for i in range(len(breaks)):
  data
.append(
      go
.Scatter(x=[None], y=[None],
      mode
='markers',
      marker
=dict(size=10,  color=zone_colors[i]),
      legendgroup
=str(legend[i]),
      showlegend
=True,
      name
=zone_names[i])
)
layout
= go.Layout(
title
=go.layout.Title(
        text
=title,
        xref
='paper',
        x
=0
   
),
)

fig
= go.Figure(data=data, layout=layout)
barplot
= plotly.offline.plot(fig, auto_open = False, filename="D:/temp.html")

## Load Plot
GC
.webpage(barplot)

Python result in :

PythonChart.png




So Questions(Help needed on):
1. Why are the Rest interval stacked (it is not a stacked bar)?
2. How to set the bar width (bar width equal to duation of the interval)?
3. I need to set the filename on a absolote path else it doesn't show (while it is creating the html my filesystem), why or is this a bug?
4. What is the roadmap of golden cheetah more R oriented or move to python?

Thanks in advance kind regards,
RB

Marco1967

unread,
Jun 22, 2019, 2:21:18 AM6/22/19
to golden-cheetah-users

super thank you!


yy
= np.asarray(intervals<span style="color: #660;" class="styled-by-pre

RB

unread,
Jun 28, 2019, 2:37:20 PM6/28/19
to golden-cheetah-users
Investigated some more so some answers on my own question

So Questions(Help needed on):
1. Why are the Rest interval stacked (it is not a stacked bar)?
    x values with the same name are mapped on each other on the x-as
2. How to set the bar width (bar width equal to duation of the interval)?
    Not possible with plotly (with bar charts), i managed to do with shapes

I would still like answer on:
3. I need to set the filename on a absolote path else it doesn't show (while it is creating the html on my filesystem), why or is this a bug?
4. What is the roadmap of golden cheetah more R oriented or move to python?


This is what I ended up with plotly and shapes:

barchart.png


Chart code:
import bisect
import plotly
import plotly.graph_objs as go
import numpy as np


#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"
if not intervals["name"]:
  type_title
= "ALL"
  intervals
= GC.activityIntervals()
 

#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"]) + ") 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


lap_names
= np.asarray(intervals["name"])
watts_y
= np.asarray(intervals["Average_Power"])
x
= np.asarray(intervals["start"])
duration
= np.asarray(intervals["Duration"])

trace0
= go.Scatter(
    x
=x,
    y
=watts_y,
    mode
='text',
    showlegend
=False,

)

data
= [trace0]
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
= []
for i in np.arange(0,len(lap_names)):
    shapes
.append(
       
{
       
'type': 'rect',
       
'x0': x[i],
       
'y0': 0,
       
'x1': x[i]+duration[i],
       
'y1': watts_y[i],
       
'fillcolor': interval_colors[i],
   
})
    annotations
.append(
        dict
(
            x
=x[i] + (duration[i]/2),
            y
=watts_y[i],
            xref
='x',
            yref
='y',
            text
=str(lap_names[i]) + "<br>" + str(avg_power_pct[i]) + "<br>" + str(duration[i]) + "s",
            showarrow
=True,
            arrowhead
=7,
            ax
=0,
            ay
=-40
       
))


layout
= go.Layout(
    title
= title,
    xaxis
= dict(
        range
=[0, max(x)],
        showgrid
=True,
        autorange
=True,
        zeroline
=False,
        showline
=False,
        ticks
='',
        showticklabels
=False

   
),
    yaxis
= dict(
        range
=[0,  max(watts_y)]
   
),
    margin
=go.layout.Margin(
        l
=50,
        r
=50,
        b
=100,
        t
=150,
        pad
=4
   
),
    shapes
=shapes,
    annotations
=annotations,

)

fig
= go.Figure(data=data, layout=layout)

plot
= plotly.offline.plot(fig, auto_open=False, filename='D:/temp_chart.html')
## Load Plot
GC
.webpage(plot)



Ale Martinez - No direct email please

unread,
Jun 28, 2019, 5:34:46 PM6/28/19
to golden-cheetah-users
El viernes, 28 de junio de 2019, 12:37:20 (UTC-6), RB escribió:
> Investigated some more so some answers on my own question
>
>
>
>
> So Questions(Help needed on):
> 1. Why are the Rest interval stacked (it is not a stacked bar)?
>     x values with the same name are mapped on each other on the x-as
>
> 2. How to set the bar width (bar width equal to duation of the interval)?
>     Not possible with plotly (with bar charts), i managed to do with shapes
>
>
> I would still like answer on:
>
> 3.
> I need to set the filename on a absolote path else it doesn't show
> (while it is creating the html on my filesystem), why or is this a bug?4.

The html file creation is necessary to use plotly offline, if you want to make your code portable the tempfile Python module may help

> What is the roadmap of golden cheetah more R oriented or move to python?

The objective was to have a similar API and functionality as far as possible but there are limitations with multi threading on R so Scripting in formulas (useful for complex custom metrics) is limited to Python.


> This is what I ended up with plotly and shapes:
>
>
>
> Chart code:
>
>
> import bisect
> import plotly
> import plotly.graph_objs as go
> import numpy as np
>
>
> #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"
> if not intervals["name"]:
>   type_title = "ALL"
>   intervals = GC.activityIntervals()
>  
>
> #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))+"%")m
Nice example, if you want to contribute the wiki is open.

RB

unread,
Jun 30, 2019, 7:42:03 AM6/30/19
to golden-cheetah-users
Nice example, if you want to contribute the wiki is open.
Thx, no problem to add the example to the wiki. Is there a example place on the wiki or should i place it in page: https://github.com/GoldenCheetah/GoldenCheetah/wiki/UG_Special-Topics_Working-with-Python

Also uploaded a refined chart to the cloud.

Mark Miller

unread,
Oct 23, 2019, 12:32:06 PM10/23/19
to golden-cheetah-users
Now that I got python working I tried these examples here and I can get the R Chart to show in GC but the the Python code does not display a chart in GC. It generates an html file on my system but it will not show in GC. I'm using macOS and 3.5-RC1

mlt.m...@googlemail.com

unread,
Oct 10, 2020, 9:37:55 AM10/10/20
to golden-cheetah-users
Its working for my on v3.6-DEV2006 if change the last two lines.

From

plot = plotly.offline.plot(fig, auto_open=False, filename='D:/temp_chart.html')
## Load Plot
GC
.webpage(plot)

To

plot = plotly.offline.plot(fig, auto_open=False, filename=temp_file.name)
## Load Plot
GC.webpage(pathlib.Path(temp_file.name).as_uri())

kub3r4

unread,
Oct 16, 2020, 6:04:25 PM10/16/20
to golden-cheetah-users
If running in Windows and have disk "D:" then it would work. But you would need to open "temp_chart.html" in browser.

But if you change only "auto_open" from False to True, it will automatically open "temp_chart.html" in your browser.

P Stroud-Baranda

unread,
Oct 19, 2020, 12:00:25 PM10/19/20
to golden-cheetah-users
Thank you for the code and explanations.
Reply all
Reply to author
Forward
0 new messages