Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to add a colorbar legend next to a calmap.calendarplot? #9

Closed
BreitA opened this issue Mar 8, 2016 · 15 comments
Closed

How to add a colorbar legend next to a calmap.calendarplot? #9

BreitA opened this issue Mar 8, 2016 · 15 comments

Comments

@BreitA
Copy link

BreitA commented Mar 8, 2016

Hi,

I'm currently heavily relying on your module to plot calendar heatmap. Your module is great but I wonder
If you know a simple way to put a colorbar for the whole figure on the left of the calendar heatmap figure.
It could also be a neat feature in your next iteration on the module to have an option to put automatically the colorbar when creating the heatmap.

@martijnvermaat
Copy link
Owner

Hi @BreitA,

Yes, this is something that I would like to have. Currently, I don't think it's even possible to do this manually outside of calmap after calling it.

An implementation in yearplot would probably start with something like this:

diff --git a/calmap/__init__.py b/calmap/__init__.py
index 1c53a8c..fddca78 100644
--- a/calmap/__init__.py
+++ b/calmap/__init__.py
@@ -181,7 +181,8 @@ def yearplot(data, year=None, how='sum', vmin=None, vmax=None, cmap='Reds',
     # Draw heatmap.
     kwargs['linewidth'] = linewidth
     kwargs['edgecolors'] = linecolor
-    ax.pcolormesh(plot_data, vmin=vmin, vmax=vmax, cmap=cmap, **kwargs)
+    mesh = ax.pcolormesh(plot_data, vmin=vmin, vmax=vmax, cmap=cmap, **kwargs)
+    ax.figure.colorbar(mesh)

     # Limit heatmap to our data.
     ax.set(xlim=(0, plot_data.shape[1]), ylim=(0, plot_data.shape[0]))

Some quick thoughts:

  1. This would automatically add another axis, which I'm not sure I like from an API point of view (currently, yearplot simply works on one axis, which it returns).
  2. I sense it will be difficult to get the sizing right, especially in the easy "just works" use case.
  3. Needs additional customization options (e.g., via a cbar_kws argument, and by providing an existing axis).
  4. Perhaps this works better on calendarplot than on yearplot?

Not sure when I'll have time to look into this. Would welcome a PR though :)

@BreitA
Copy link
Author

BreitA commented Mar 9, 2016

I cooked something this evening that works, I will send you what I've done when it's clean.

@BreitA
Copy link
Author

BreitA commented Mar 9, 2016

Okay so it's very "homemade" but does the job. I'm very bad with matplotlib so I'm sure there is a more elegant way to do the stuff but it works wonders for me atm. as you can see I didt it as an external function to call after the calmap plot.
The function add another axis cb_ax that is the colorbar to the figure.
This is the copy of my notebook
EDIT : update 2016-03-10 a little debug going through after testing it at work + most options tested + a little bit of pep8
ISSUES :

  • when the calendar plots autoscale to fit the figure the colorbar doesn't autoscale too (see last example)
def add_colorbar(data, fig, cmap, auto_tick=True,
                 vmin=None,
                 vmax=None,
                 num_label=3,
                 cb_pos=None,
                 decimal=0,
                 yticks=None,
                 ytick_labels=None,
                 cbar_width=0.01):
    # colorbar position
    if cb_pos is None:
        axes = fig.get_axes()
        if len(axes) > 1:
            pos1 = axes[0].get_position()
            pos2 = axes[-1].get_position()

            cb_pos = [pos1.x1 + 0.05, pos2.y0, cbar_width, pos1.y1 - pos2.y0]
        else:
            cb_pos = [0.9 + 0.05, 0.4, cbar_width, 0.22]  # fine custom pos for the single year plot object

    # custom generation of colorbar (seen in http://matplotlib.org/examples/color/colormaps_reference.html) 
    gradient = np.linspace(0, 255, 256)
    gradient = np.vstack((gradient, gradient))
    # generate colorbar ax
    cb_ax = fig.add_axes(cb_pos)
    cb_ax.imshow(gradient.T, aspect='auto', cmap=plt.get_cmap(cmap))

    # generate tick and tick label by hand (I'm sure there is a much more efficient thing to do here)
    if vmin is None:
        vmin = np.min(data)
    if vmax is None:
        vmax = np.max(data)
    vmax = float(vmax)
    vmin = float(vmin)
    if auto_tick is True:

        step = (vmax - vmin) / (num_label - 1)
        ytick_labels = np.arange(vmin, vmax, step)
        ytick_labels = np.append(ytick_labels, vmax)
        ytick_labels = np.round(ytick_labels, decimal)
        ytick_labels = [str(v) for v in ytick_labels.tolist()]
        yticks = tuple(np.linspace(0, 255, num_label).tolist())

    elif yticks is None or ytick_labels is None:

        raise TypeError('yticks and ytick_labels required if auto_tick is False')
    elif len(yticks) != len(ytick_labels):

        raise TypeError('yticks and ytick_labels must be of same length')
    else:
        # custom ticks
        num_label = len(yticks)
        yticks = np.asarray(yticks) - vmin
        yticks = yticks * 255 / (vmax - vmin)
    # edit ytick + remove xticks  
    cb_ax.set_yticks(yticks)
    cb_ax.set_xticks([])
    cb_ax.set_yticklabels(ytick_labels)
    cb_ax.yaxis.tick_right()
    return cb_ax




#few years example
df=pd.DataFrame(data=np.random.randn(900,1)
                ,index=pd.date_range(start='2014-01-01 00:00:00',freq='1D',periods =900)
                ,columns=['data'])
cmap='RdYlGn'
fig,ax=calmap.calendarplot(df['data'],
                    fillcolor='grey', linewidth=0,cmap=cmap,
                    fig_kws=dict(figsize=(17,8)))
fig.suptitle('Calendar view' ,fontsize=20,y =1.08)

cb_ax=add_colorbar(df['data'],fig,cmap=cmap)

cb_ax.tick_params(axis='y', which='major', labelsize=15)
cb_ax.set_title('rand_data',fontsize=15,y=1.02)      

#few years example custom
df=pd.DataFrame(data=np.random.randn(900,1)
                ,index=pd.date_range(start='2014-01-01 00:00:00',freq='1D',periods =900)
                ,columns=['data'])
cmap='RdYlGn'
yticks=[-5,1,2,4.5]
ytick_labels=['-5 git','1 git','2 git','4.5 git' ]
fig,ax=calmap.calendarplot(df['data'],
                    fillcolor='grey', linewidth=0,cmap=cmap,
                    fig_kws=dict(figsize=(17,8)),vmin=-5,vmax=5)
fig.suptitle('Calendar view' ,fontsize=20,y =1.08)

cb_ax=add_colorbar(df['data'],fig,cmap=cmap,vmin=-5,vmax=5,decimal=1,
                   yticks=yticks,
                   ytick_labels=ytick_labels,
                   auto_tick=False,
                  cbar_width=0.03)

cb_ax.tick_params(axis='y', which='major', labelsize=15)
cb_ax.set_title('rand_data',fontsize=15,y=1.02)       

# lots of years example
df=pd.DataFrame(data=np.random.randn(2000,1)
                ,index=pd.date_range(start='2014-01-01 00:00:00',freq='1D',periods =2000)
                ,columns=['data'])
cmap='RdYlGn'
fig,ax=calmap.calendarplot(df['data'],
                    fillcolor='grey', linewidth=0,cmap=cmap,
                    fig_kws=dict(figsize=(17,17)))
fig.suptitle('Calendar view' ,fontsize=20,y =1.08)

cb_ax=add_colorbar(df['data'],fig,cmap)

cb_ax.tick_params(axis='y', which='major', labelsize=15)
cb_ax.set_title('rand_data',fontsize=15,y=1.02)       

# yearplot test
df=pd.DataFrame(data=np.random.randn(365,1)
                ,index=pd.date_range(start='2014-01-01 00:00:00',freq='1D',periods =365)
                ,columns=['data'])
cmap='RdYlGn'

fig=plt.figure(figsize=(17,8))

ax=calmap.yearplot(df['data'],
                    fillcolor='grey', linewidth=0,cmap=cmap,
                    )

cb_ax=add_colorbar(df['data'],fig,cmap)
cb_ax.tick_params(axis='y', which='major', labelsize=15)
cb_ax.set_title('rand_data',fontsize=15,y=1.08)   
fig.suptitle('Calendar view' ,fontsize=20,y=0.7)

# lots of years example bad scale 
df=pd.DataFrame(data=np.random.randn(2000,1)
                ,index=pd.date_range(start='2014-01-01 00:00:00',freq='1D',periods =2000)
                ,columns=['data'])
cmap='RdYlGn'
fig,ax=calmap.calendarplot(df['data'],
                    fillcolor='grey', linewidth=0,cmap=cmap,
                    fig_kws=dict(figsize=(17,8)))
fig.suptitle('Calendar view' ,fontsize=20,y =1.08)

cb_ax=add_colorbar(df['data'],fig,cmap)

cb_ax.tick_params(axis='y', which='major', labelsize=15)
cb_ax.set_title('rand_data',fontsize=15,y=1.02)       

@martijnvermaat
Copy link
Owner

@BreitA Thanks a lot for sharing your code.

I don't have the time to work on it now, but I would like to include something like you're showing here in the future. So I'm leaving this issue open, thanks again 👍

@kidpixo
Copy link

kidpixo commented Jul 27, 2016

Hi, I was searching for the same stuff and I didn't think about looking here in the issues!

I answer to a question on stackoverflow python 2.7 - add a colorbar to a calmap plot and I think it's yours, @BreitA .

My solution is to pick the children of the axis returned by yearplot and stick the colorbar to this mappable : after digging in the code, it is the second call to ax.quadmesh . So the code would be:

fig,ax=calmap.calendarplot(df['data'],
                    fillcolor='grey', linewidth=0,cmap='RdYlGn',
                    fig_kws=dict(figsize=(17,8)))

fig.colorbar(ax[0].get_children()[1], ax=ax.ravel().tolist())

and even simpler for a single yearplot (it is encapsulated in an explicit call to figure to declare the size) :

fig = plt.figure(figsize=(20,8))
ax = fig.add_subplot(111)
cax = calmap.yearplot(df, year=2014, ax=ax, cmap='YlGn')
fig.colorbar(cax.get_children()[1], ax=cax, orientation='horizontal')

A good start would be to return the reference to the actual graphic object that contains the data in the yearplot function

@martijnvermaat
Copy link
Owner

Thanks @kidpixo for commenting here and linking back to the SO thread!

I'm interested in adding this or making it easier to do it yourself, but time limited at the moment.

@kidpixo
Copy link

kidpixo commented Jul 27, 2016

Thank you for the package, @martijnvermaat !

A solution would be to alter the return of yearplot, I don't know how this impact back compatibility.

I can also think about an helper function, like yearplotcolorbar who takes the output of yearplot and wrap the code I used to generate the plot.

Something more elaborated for calendarplot should do the trick.

@BreitA
Copy link
Author

BreitA commented Aug 18, 2016

Thanks for the solution provided @kidpixo , will try it when I have the time ! And yes the Stackoverflow was mine and then I ended up asking the question here.

@kidpixo
Copy link

kidpixo commented Aug 22, 2016

@BreitA you are welcome! Could you please accept my solution on stackoverflow?

@BreitA
Copy link
Author

BreitA commented Aug 22, 2016

Yeah I just did! I tested it and it is exactly what I was looking for and it works great!

@kidpixo
Copy link

kidpixo commented Aug 22, 2016

Nice to hear :-D if you have time to contribute or to publish some code of your solution will be great!

@BreitA
Copy link
Author

BreitA commented Aug 22, 2016

Honestly at this point your solution is much more elegant and show a better understanding of matplotlib than I have done with my homemade solution.
I will use your solution for my future use of this lib tbh.

Thanks again.

@dnaneet
Copy link

dnaneet commented Nov 27, 2019

> fig = plt.figure(figsize=(20,8))
> ax = fig.add_subplot(111)
> cax = calmap.yearplot(df, year=2014, ax=ax, cmap='YlGn')
> fig.colorbar(cax.get_children()[1], ax=cax, orientation='horizontal')
> ```
> 

This works beautifully!

@MarvinT
Copy link

MarvinT commented Jul 10, 2021

Hi, if this problem still exists and you'd like to create a PR to fix it please direct it to https://github.com/MarvinT/calmap/
That is the version that gets published to pypi and has received several updates to fix some existing issues.

@martijnvermaat
Copy link
Owner

Thank you for creating the issue. Unfortunately I don't have the time to maintain this project. As per @MarvinT 's comment, please see https://github.com/MarvinT/calmap/ instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants