Repository: martijnvermaat/calmap
Branch: master
Commit: 37c1046b0ec4
Files: 17
Total size: 64.9 KB
Directory structure:
gitextract_ftj_oh14/
├── .gitignore
├── .travis.yml
├── AUTHORS.rst
├── LICENSE.rst
├── MANIFEST.in
├── README.rst
├── calmap/
│ └── __init__.py
├── doc/
│ ├── .gitignore
│ ├── Makefile
│ ├── conf.py
│ ├── index.rst
│ ├── requirements.txt
│ └── sphinxext/
│ └── plot_directive.py
├── setup.py
└── tests/
├── conftest.py
├── requirements.txt
└── test_calmap.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*.pyc
build
dist
.cache
*.egg-info
================================================
FILE: .travis.yml
================================================
# Validate this file using http://lint.travis-ci.org/
language: python
sudo: false
cache: pip
python:
- "2.7"
- "3.4"
- "3.5"
install:
- pip install -r tests/requirements.txt -r doc/requirements.txt
- python setup.py install
script:
- py.test --mpl
- pushd doc && make html && popd
deploy:
provider: pypi
distributions: "sdist bdist_wheel --universal"
docs_dir: doc/.build/html
user: martijnvermaat
password:
secure: hPSuyetI+FkAzGkDnMlGJBvlDXfZS48y6onsH3cEcl3GN++3R3UsvNYywbJvCty38H3Odg5hjVaDUwxO4ozd0IvaGj/E2KkYrLAUrCeq4VSIp4HCr9jUXwIOWcfJY5gfq7t+xr68LmOlxinc9NOwLwePC49SOdE7Nj4VK8/ajnmUc9/1hlCVsddqs3I8T+soOamhNiJMwe+rDyEY6GI9dANibGxsJYKF5pa9mp1xE/eY4nUMVzXj28mijBIGeZkwzYNEcMvylOXwHu8lb/33JPVympld0s+qTL30Bjww72SlDtkLJwexYhBFCdZ+oPrV8xT8c4czWPwTgstWPAEwsCEW6m5rVwC0/jxclUMYM4CHIvUMUYSDjs4y0JjDo86LyfZqn3Dtiov40NHZbnE3zZvg3uICCxtQB1bbCjgM7QGbiUNK90OdSQU2JFKpWpbtK+c4JR9GlFrQfAOi4dIPEe2Bncws+uA+WbIKDSTm6XvEwiZYckEH6tPQ9v1/Drk28zMygRO9/ydCmtPBT7p6TXJGboPAztIPHs04dLSOl7tdfKp6SqjjBb3beBWTGEqz9unNf92WMIw76nvZC790XzLc7NXuo02JDFwuZIoFAvWbxo56sr0VW2NfPENAuv+vE8AF3jPb5xjOfzqoOPiEP7xlZ8mUm8/idz55ykKz2sU=
skip_cleanup: true
on:
tags: true
python: "2.7"
================================================
FILE: AUTHORS.rst
================================================
Calmap is written and maintained by Martijn Vermaat.
- Martijn Vermaat <martijn@vermaat.name>
================================================
FILE: LICENSE.rst
================================================
Copyright (c) 2015 by Martijn Vermaat and contributors (see AUTHORS.rst
for details).
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: MANIFEST.in
================================================
include AUTHORS.rst CHANGES.rst LICENSE.rst README.rst
================================================
FILE: README.rst
================================================
| **Note:** See `MarvinT/calmap <https://github.com/MarvinT/calmap/>`_ for the maintained version of the project. That is also the version that gets published to PyPI and it has received several fixes to issues.
Calendar heatmaps from Pandas time series data
==============================================
Plot `Pandas <http://pandas.pydata.org/>`_ time series data sampled by day in
a heatmap per calendar year, similar to GitHub's contributions plot, using
`matplotlib <http://matplotlib.org/>`_.
.. image:: https://pythonhosted.org/calmap/_images/index-2.png
:alt: Example calendar heatmap
Usage
-----
See the `documentation <https://pythonhosted.org/calmap>`_.
Installation
------------
To install the latest release via PyPI using pip::
pip install calmap
================================================
FILE: calmap/__init__.py
================================================
"""
Calendar heatmaps from Pandas time series data.
Plot Pandas time series data sampled by day in a heatmap per calendar year,
similar to GitHub's contributions calendar.
"""
from __future__ import unicode_literals
import calendar
import datetime
from matplotlib.colors import ColorConverter, ListedColormap
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from distutils.version import StrictVersion
__version_info__ = ('0', '0', '7', 'dev')
__date__ = '14 Feb 2016'
__version__ = '.'.join(__version_info__)
__author__ = 'Martijn Vermaat'
__contact__ = 'martijn@vermaat.name'
__homepage__ = 'https://github.com/martijnvermaat/calmap'
_pandas_18 = StrictVersion(pd.__version__) >= StrictVersion('0.18')
def yearplot(data, year=None, how='sum', vmin=None, vmax=None, cmap='Reds',
fillcolor='whitesmoke', linewidth=1, linecolor=None,
daylabels=calendar.day_abbr[:], dayticks=True,
monthlabels=calendar.month_abbr[1:], monthticks=True, ax=None,
**kwargs):
"""
Plot one year from a timeseries as a calendar heatmap.
Parameters
----------
data : Series
Data for the plot. Must be indexed by a DatetimeIndex.
year : integer
Only data indexed by this year will be plotted. If `None`, the first
year for which there is data will be plotted.
how : string
Method for resampling data by day. If `None`, assume data is already
sampled by day and don't resample. Otherwise, this is passed to Pandas
`Series.resample`.
vmin, vmax : floats
Values to anchor the colormap. If `None`, min and max are used after
resampling data by day.
cmap : matplotlib colormap name or object
The mapping from data values to color space.
fillcolor : matplotlib color
Color to use for days without data.
linewidth : float
Width of the lines that will divide each day.
linecolor : color
Color of the lines that will divide each day. If `None`, the axes
background color is used, or 'white' if it is transparent.
daylabels : list
Strings to use as labels for days, must be of length 7.
dayticks : list or int or bool
If `True`, label all days. If `False`, don't label days. If a list,
only label days with these indices. If an integer, label every n day.
monthlabels : list
Strings to use as labels for months, must be of length 12.
monthticks : list or int or bool
If `True`, label all months. If `False`, don't label months. If a
list, only label months with these indices. If an integer, label every
n month.
ax : matplotlib Axes
Axes in which to draw the plot, otherwise use the currently-active
Axes.
kwargs : other keyword arguments
All other keyword arguments are passed to matplotlib `ax.pcolormesh`.
Returns
-------
ax : matplotlib Axes
Axes object with the calendar heatmap.
Examples
--------
By default, `yearplot` plots the first year and sums the values per day:
.. plot::
:context: close-figs
calmap.yearplot(events)
We can choose which year is plotted with the `year` keyword argment:
.. plot::
:context: close-figs
calmap.yearplot(events, year=2015)
The appearance can be changed by using another colormap. Here we also use
a darker fill color for days without data and remove the lines:
.. plot::
:context: close-figs
calmap.yearplot(events, cmap='YlGn', fillcolor='grey',
linewidth=0)
The axis tick labels can look a bit crowded. We can ask to draw only every
nth label, or explicitely supply the label indices. The labels themselves
can also be customized:
.. plot::
:context: close-figs
calmap.yearplot(events, monthticks=3, daylabels='MTWTFSS',
dayticks=[0, 2, 4, 6])
"""
if year is None:
year = data.index.sort_values()[0].year
if how is None:
# Assume already sampled by day.
by_day = data
else:
# Sample by day.
if _pandas_18:
by_day = data.resample('D').agg(how)
else:
by_day = data.resample('D', how=how)
# Min and max per day.
if vmin is None:
vmin = by_day.min()
if vmax is None:
vmax = by_day.max()
if ax is None:
ax = plt.gca()
if linecolor is None:
# Unfortunately, linecolor cannot be transparent, as it is drawn on
# top of the heatmap cells. Therefore it is only possible to mimic
# transparent lines by setting them to the axes background color. This
# of course won't work when the axes itself has a transparent
# background so in that case we default to white which will usually be
# the figure or canvas background color.
linecolor = ax.get_fc()
# By: Ryan Susman - "ax.get_axis_bgcolor()" is deprecated, see:
# https://matplotlib.org/2.1.2/api/_as_gen/matplotlib.axes.Axes.get_axis_bgcolor.html
if ColorConverter().to_rgba(linecolor)[-1] == 0:
linecolor = 'white'
# Filter on year.
by_day = by_day[str(year)]
# Add missing days.
by_day = by_day.reindex(
pd.date_range(start=str(year), end=str(year + 1), freq='D')[:-1])
# Create data frame we can pivot later.
by_day = pd.DataFrame({'data': by_day,
'fill': 1,
'day': by_day.index.dayofweek,
'week': by_day.index.week})
# There may be some days assigned to previous year's last week or
# next year's first week. We create new week numbers for them so
# the ordering stays intact and week/day pairs unique.
by_day.loc[(by_day.index.month == 1) & (by_day.week > 50), 'week'] = 0
by_day.loc[(by_day.index.month == 12) & (by_day.week < 10), 'week'] \
= by_day.week.max() + 1
# Pivot data on day and week and mask NaN days.
plot_data = by_day.pivot('day', 'week', 'data').values[::-1]
plot_data = np.ma.masked_where(np.isnan(plot_data), plot_data)
# Do the same for all days of the year, not just those we have data for.
fill_data = by_day.pivot('day', 'week', 'fill').values[::-1]
fill_data = np.ma.masked_where(np.isnan(fill_data), fill_data)
# Draw heatmap for all days of the year with fill color.
ax.pcolormesh(fill_data, vmin=0, vmax=1, cmap=ListedColormap([fillcolor]))
# Draw heatmap.
kwargs['linewidth'] = linewidth
kwargs['edgecolors'] = linecolor
ax.pcolormesh(plot_data, vmin=vmin, vmax=vmax, cmap=cmap, **kwargs)
# Limit heatmap to our data.
ax.set(xlim=(0, plot_data.shape[1]), ylim=(0, plot_data.shape[0]))
# Square cells.
ax.set_aspect('equal')
# Remove spines and ticks.
for side in ('top', 'right', 'left', 'bottom'):
ax.spines[side].set_visible(False)
ax.xaxis.set_tick_params(which='both', length=0)
ax.yaxis.set_tick_params(which='both', length=0)
# Get indices for monthlabels.
if monthticks is True:
monthticks = range(len(monthlabels))
elif monthticks is False:
monthticks = []
elif isinstance(monthticks, int):
monthticks = range(len(monthlabels))[monthticks // 2::monthticks]
# Get indices for daylabels.
if dayticks is True:
dayticks = range(len(daylabels))
elif dayticks is False:
dayticks = []
elif isinstance(dayticks, int):
dayticks = range(len(daylabels))[dayticks // 2::dayticks]
ax.set_xlabel('')
ax.set_xticks([by_day.loc[datetime.date(year, i + 1, 15).strftime("%Y-%m-%d"), 'week']
for i in monthticks])
# df.ix is deprecated see:
# https://pandas.pydata.org/pandas-docs/version/0.23/generated/pandas.DataFrame.ix.html
ax.set_xticklabels([monthlabels[i] for i in monthticks], ha='center')
ax.set_ylabel('')
ax.yaxis.set_ticks_position('right')
ax.set_yticks([6 - i + 0.5 for i in dayticks])
ax.set_yticklabels([daylabels[i] for i in dayticks], rotation='horizontal',
va='center')
return ax
def calendarplot(data, how='sum', yearlabels=True, yearascending=True, yearlabel_kws=None,
subplot_kws=None, gridspec_kws=None, fig_kws=None, **kwargs):
"""
Plot a timeseries as a calendar heatmap.
Parameters
----------
data : Series
Data for the plot. Must be indexed by a DatetimeIndex.
how : string
Method for resampling data by day. If `None`, assume data is already
sampled by day and don't resample. Otherwise, this is passed to Pandas
`Series.resample`.
yearlabels : bool
Whether or not to draw the year for each subplot.
yearascending : bool
Sort the calendar in ascending or descending order.
yearlabel_kws : dict
Keyword arguments passed to the matplotlib `set_ylabel` call which is
used to draw the year for each subplot.
subplot_kws : dict
Keyword arguments passed to the matplotlib `add_subplot` call used to
create each subplot.
gridspec_kws : dict
Keyword arguments passed to the matplotlib `GridSpec` constructor used
to create the grid the subplots are placed on.
fig_kws : dict
Keyword arguments passed to the matplotlib `figure` call.
kwargs : other keyword arguments
All other keyword arguments are passed to `yearplot`.
Returns
-------
fig, axes : matplotlib Figure and Axes
Tuple where `fig` is the matplotlib Figure object `axes` is an array
of matplotlib Axes objects with the calendar heatmaps, one per year.
Examples
--------
With `calendarplot` we can plot several years in one figure:
.. plot::
:context: close-figs
calmap.calendarplot(events)
"""
yearlabel_kws = yearlabel_kws or {}
subplot_kws = subplot_kws or {}
gridspec_kws = gridspec_kws or {}
fig_kws = fig_kws or {}
years = np.unique(data.index.year)
if not yearascending:
years = years[::-1]
fig, axes = plt.subplots(nrows=len(years), ncols=1, squeeze=False,
subplot_kw=subplot_kws,
gridspec_kw=gridspec_kws, **fig_kws)
axes = axes.T[0]
# We explicitely resample by day only once. This is an optimization.
if how is None:
by_day = data
else:
if _pandas_18:
by_day = data.resample('D').agg(how)
else:
by_day = data.resample('D', how=how)
ylabel_kws = dict(
fontsize=32,
color=kwargs.get('fillcolor', 'whitesmoke'),
fontweight='bold',
fontname='Arial',
ha='center')
ylabel_kws.update(yearlabel_kws)
max_weeks = 0
for year, ax in zip(years, axes):
yearplot(by_day, year=year, how=None, ax=ax, **kwargs)
max_weeks = max(max_weeks, ax.get_xlim()[1])
if yearlabels:
ax.set_ylabel(str(year), **ylabel_kws)
# In a leap year it might happen that we have 54 weeks (e.g., 2012).
# Here we make sure the width is consistent over all years.
for ax in axes:
ax.set_xlim(0, max_weeks)
# Make the axes look good.
plt.tight_layout()
return fig, axes
================================================
FILE: doc/.gitignore
================================================
.build
================================================
FILE: doc/Makefile
================================================
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = .build
# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
endif
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " applehelp to make an Apple Help Book"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
@echo " coverage to run coverage check of the documentation (if enabled)"
clean:
rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Calmap.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Calmap.qhc"
applehelp:
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
@echo
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
@echo "N.B. You won't be able to view it unless you put it in" \
"~/Library/Documentation/Help or install it in your application" \
"bundle."
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/Calmap"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Calmap"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
coverage:
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
@echo "Testing of coverage in the sources finished, look at the " \
"results in $(BUILDDIR)/coverage/python.txt."
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
================================================
FILE: doc/conf.py
================================================
# -*- coding: utf-8 -*-
#
# Calmap documentation build configuration file, created by
# sphinx-quickstart on Thu Nov 26 16:51:51 2015.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys
import os
import matplotlib as mpl
mpl.use('Agg')
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('..'))
# -- General configuration ------------------------------------------------
import calmap
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
# The plot_directive extension is a modification of the one bundled with
# matplotlib, adding a fig.tight_layout() call and setting bbox_inches
# to 'tight' on the savefig call.
sys.path.insert(0, os.path.abspath('sphinxext'))
extensions = [
'plot_directive',
'sphinx.ext.autodoc',
'sphinx.ext.viewcode',
'numpydoc'
]
# Configuration for plot_directive.
plot_include_source = True
plot_formats = [("png", 90)]
plot_html_show_formats = False
plot_html_show_source_link = False
# Add any paths that contain templates here, relative to this directory.
templates_path = ['.templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'Calmap'
copyright = u'2015, %s' % calmap.__author__
author = calmap.__author__
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '.'.join(calmap.__version__.split('.')[:2])
# The full version, including alpha/beta/rc tags.
release = calmap.__version__
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['.build']
# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'alabaster'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
html_theme_options = {
'github_banner': True,
'github_user': 'martijnvermaat',
'github_repo': 'calmap'
}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['.static']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Language to be used for generating the HTML full-text search index.
# Sphinx supports the following languages:
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
#html_search_language = 'en'
# A dictionary with options for the search language support, empty by default.
# Now only 'ja' uses this config value
#html_search_options = {'type': 'default'}
# The name of a javascript file (relative to the configuration directory) that
# implements a search results scorer. If empty, the default will be used.
#html_search_scorer = 'scorer.js'
# Output file base name for HTML help builder.
htmlhelp_basename = 'Calmapdoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
# Latex figure (float) alignment
#'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'Calmap.tex', u'Calmap Documentation',
u'Martijn Vermaat', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'calmap', u'Calmap Documentation',
[author], 1)
]
# If true, show URL addresses after external links.
#man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'Calmap', u'Calmap Documentation',
author, 'Calmap', 'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False
================================================
FILE: doc/index.rst
================================================
.. currentmodule:: calmap
Calendar heatmaps from Pandas time series data
==============================================
Plot `Pandas <http://pandas.pydata.org/>`_ time series data sampled by day in
a heatmap per calendar year, similar to GitHub's contributions plot, using
`matplotlib <http://matplotlib.org/>`_.
Usage
-----
Assume we have some weighted events as a Pandas Series with a
DatetimeIndex. They could be Git commits (with the diff size as weight),
mileage of your runs, or minutes spent on telemarketing phone calls driving
you crazy.
For illustration purposes we just create 500 events as random float values
assigned to random days over a 700-day period:
.. plot::
:context: close-figs
import numpy as np; np.random.seed(sum(map(ord, 'calmap')))
import pandas as pd
import calmap
all_days = pd.date_range('1/15/2014', periods=700, freq='D')
days = np.random.choice(all_days, 500)
events = pd.Series(np.random.randn(len(days)), index=days)
Using :func:`yearplot`, we can easily plot a heatmap of these events over a
year:
.. plot::
:context: close-figs
calmap.yearplot(events, year=2015)
Or we can use :func:`calendarplot` to plot all years as subplots into one
figure:
.. plot::
:context: close-figs
calmap.calendarplot(events, monthticks=3, daylabels='MTWTFSS',
dayticks=[0, 2, 4, 6], cmap='YlGn',
fillcolor='grey', linewidth=0,
fig_kws=dict(figsize=(8, 4)))
See the :ref:`API documentation <api>` for more information and examples.
Installation
------------
To install the latest release via PyPI using pip::
pip install calmap
The latest development version `can be found on GitHub
<https://github.com/martijnvermaat/calmap>`_.
.. _api:
API documentation
-----------------
.. module:: calmap
.. autofunction:: yearplot
.. autofunction:: calendarplot
Copyright
---------
This library is licensed under the MIT License, meaning you can do whatever
you want with it as long as all copies include these license terms. The full
license text can be found in the LICENSE.rst file. See the AUTHORS.rst for for
a complete list of copyright holders.
================================================
FILE: doc/requirements.txt
================================================
matplotlib>=1.5.0
numpydoc
Sphinx
================================================
FILE: doc/sphinxext/plot_directive.py
================================================
"""
A directive for including a matplotlib plot in a Sphinx document.
By default, in HTML output, `plot` will include a .png file with a
link to a high-res .png and .pdf. In LaTeX output, it will include a
.pdf.
The source code for the plot may be included in one of three ways:
1. **A path to a source file** as the argument to the directive::
.. plot:: path/to/plot.py
When a path to a source file is given, the content of the
directive may optionally contain a caption for the plot::
.. plot:: path/to/plot.py
This is the caption for the plot
Additionally, one may specify the name of a function to call (with
no arguments) immediately after importing the module::
.. plot:: path/to/plot.py plot_function1
2. Included as **inline content** to the directive::
.. plot::
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np
img = mpimg.imread('_static/stinkbug.png')
imgplot = plt.imshow(img)
3. Using **doctest** syntax::
.. plot::
A plotting example:
>>> import matplotlib.pyplot as plt
>>> plt.plot([1,2,3], [4,5,6])
Options
-------
The ``plot`` directive supports the following options:
format : {'python', 'doctest'}
Specify the format of the input
include-source : bool
Whether to display the source code. The default can be changed
using the `plot_include_source` variable in conf.py
encoding : str
If this source file is in a non-UTF8 or non-ASCII encoding,
the encoding must be specified using the `:encoding:` option.
The encoding will not be inferred using the ``-*- coding -*-``
metacomment.
context : bool or str
If provided, the code will be run in the context of all
previous plot directives for which the `:context:` option was
specified. This only applies to inline code plot directives,
not those run from files. If the ``:context: reset`` option is
specified, the context is reset for this and future plots, and
previous figures are closed prior to running the code.
``:context:close-figs`` keeps the context but closes previous figures
before running the code.
nofigs : bool
If specified, the code block will be run, but no figures will
be inserted. This is usually useful with the ``:context:``
option.
Additionally, this directive supports all of the options of the
`image` directive, except for `target` (since plot will add its own
target). These include `alt`, `height`, `width`, `scale`, `align` and
`class`.
Configuration options
---------------------
The plot directive has the following configuration options:
plot_include_source
Default value for the include-source option
plot_html_show_source_link
Whether to show a link to the source in HTML.
plot_pre_code
Code that should be executed before each plot.
plot_basedir
Base directory, to which ``plot::`` file names are relative
to. (If None or empty, file names are relative to the
directory where the file containing the directive is.)
plot_formats
File formats to generate. List of tuples or strings::
[(suffix, dpi), suffix, ...]
that determine the file format and the DPI. For entries whose
DPI was omitted, sensible defaults are chosen. When passing from
the command line through sphinx_build the list should be passed as
suffix:dpi,suffix:dpi, ....
plot_html_show_formats
Whether to show links to the files in HTML.
plot_rcparams
A dictionary containing any non-standard rcParams that should
be applied before each plot.
plot_apply_rcparams
By default, rcParams are applied when `context` option is not used in
a plot directive. This configuration option overrides this behavior
and applies rcParams before each plot.
plot_working_directory
By default, the working directory will be changed to the directory of
the example, so the code can get at its data files, if any. Also its
path will be added to `sys.path` so it can import any helper modules
sitting beside it. This configuration option can be used to specify
a central directory (also added to `sys.path`) where data files and
helper modules for all code are located.
plot_template
Provide a customized template for preparing restructured text.
"""
from __future__ import (absolute_import, division, print_function,
unicode_literals)
from matplotlib.externals import six
from matplotlib.externals.six.moves import xrange
import sys, os, shutil, io, re, textwrap
from os.path import relpath
import traceback
import warnings
if not six.PY3:
import cStringIO
from docutils.parsers.rst import directives
from docutils.parsers.rst.directives.images import Image
align = Image.align
import sphinx
sphinx_version = sphinx.__version__.split(".")
# The split is necessary for sphinx beta versions where the string is
# '6b1'
sphinx_version = tuple([int(re.split('[^0-9]', x)[0])
for x in sphinx_version[:2]])
try:
# Sphinx depends on either Jinja or Jinja2
import jinja2
def format_template(template, **kw):
return jinja2.Template(template).render(**kw)
except ImportError:
import jinja
def format_template(template, **kw):
return jinja.from_string(template, **kw)
import matplotlib
import matplotlib.cbook as cbook
try:
with warnings.catch_warnings(record=True):
warnings.simplefilter("error", UserWarning)
matplotlib.use('Agg')
except UserWarning:
import matplotlib.pyplot as plt
plt.switch_backend("Agg")
else:
import matplotlib.pyplot as plt
from matplotlib import _pylab_helpers
__version__ = 2
#------------------------------------------------------------------------------
# Registration hook
#------------------------------------------------------------------------------
def plot_directive(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
return run(arguments, content, options, state_machine, state, lineno)
plot_directive.__doc__ = __doc__
def _option_boolean(arg):
if not arg or not arg.strip():
# no argument given, assume used as a flag
return True
elif arg.strip().lower() in ('no', '0', 'false'):
return False
elif arg.strip().lower() in ('yes', '1', 'true'):
return True
else:
raise ValueError('"%s" unknown boolean' % arg)
def _option_context(arg):
if arg in [None, 'reset', 'close-figs']:
return arg
raise ValueError("argument should be None or 'reset' or 'close-figs'")
def _option_format(arg):
return directives.choice(arg, ('python', 'doctest'))
def _option_align(arg):
return directives.choice(arg, ("top", "middle", "bottom", "left", "center",
"right"))
def mark_plot_labels(app, document):
"""
To make plots referenceable, we need to move the reference from
the "htmlonly" (or "latexonly") node to the actual figure node
itself.
"""
for name, explicit in six.iteritems(document.nametypes):
if not explicit:
continue
labelid = document.nameids[name]
if labelid is None:
continue
node = document.ids[labelid]
if node.tagname in ('html_only', 'latex_only'):
for n in node:
if n.tagname == 'figure':
sectname = name
for c in n:
if c.tagname == 'caption':
sectname = c.astext()
break
node['ids'].remove(labelid)
node['names'].remove(name)
n['ids'].append(labelid)
n['names'].append(name)
document.settings.env.labels[name] = \
document.settings.env.docname, labelid, sectname
break
def setup(app):
setup.app = app
setup.config = app.config
setup.confdir = app.confdir
options = {'alt': directives.unchanged,
'height': directives.length_or_unitless,
'width': directives.length_or_percentage_or_unitless,
'scale': directives.nonnegative_int,
'align': _option_align,
'class': directives.class_option,
'include-source': _option_boolean,
'format': _option_format,
'context': _option_context,
'nofigs': directives.flag,
'encoding': directives.encoding
}
app.add_directive('plot', plot_directive, True, (0, 2, False), **options)
app.add_config_value('plot_pre_code', None, True)
app.add_config_value('plot_include_source', False, True)
app.add_config_value('plot_html_show_source_link', True, True)
app.add_config_value('plot_formats', ['png', 'hires.png', 'pdf'], True)
app.add_config_value('plot_basedir', None, True)
app.add_config_value('plot_html_show_formats', True, True)
app.add_config_value('plot_rcparams', {}, True)
app.add_config_value('plot_apply_rcparams', False, True)
app.add_config_value('plot_working_directory', None, True)
app.add_config_value('plot_template', None, True)
app.connect(str('doctree-read'), mark_plot_labels)
#------------------------------------------------------------------------------
# Doctest handling
#------------------------------------------------------------------------------
def contains_doctest(text):
try:
# check if it's valid Python as-is
compile(text, '<string>', 'exec')
return False
except SyntaxError:
pass
r = re.compile(r'^\s*>>>', re.M)
m = r.search(text)
return bool(m)
def unescape_doctest(text):
"""
Extract code from a piece of text, which contains either Python code
or doctests.
"""
if not contains_doctest(text):
return text
code = ""
for line in text.split("\n"):
m = re.match(r'^\s*(>>>|\.\.\.) (.*)$', line)
if m:
code += m.group(2) + "\n"
elif line.strip():
code += "# " + line.strip() + "\n"
else:
code += "\n"
return code
def split_code_at_show(text):
"""
Split code at plt.show()
"""
parts = []
is_doctest = contains_doctest(text)
part = []
for line in text.split("\n"):
if (not is_doctest and line.strip() == 'plt.show()') or \
(is_doctest and line.strip() == '>>> plt.show()'):
part.append(line)
parts.append("\n".join(part))
part = []
else:
part.append(line)
if "\n".join(part).strip():
parts.append("\n".join(part))
return parts
def remove_coding(text):
"""
Remove the coding comment, which six.exec_ doesn't like.
"""
sub_re = re.compile("^#\s*-\*-\s*coding:\s*.*-\*-$", flags=re.MULTILINE)
return sub_re.sub("", text)
#------------------------------------------------------------------------------
# Template
#------------------------------------------------------------------------------
TEMPLATE = """
{{ source_code }}
{{ only_html }}
{% if source_link or (html_show_formats and not multi_image) %}
(
{%- if source_link -%}
`Source code <{{ source_link }}>`__
{%- endif -%}
{%- if html_show_formats and not multi_image -%}
{%- for img in images -%}
{%- for fmt in img.formats -%}
{%- if source_link or not loop.first -%}, {% endif -%}
`{{ fmt }} <{{ dest_dir }}/{{ img.basename }}.{{ fmt }}>`__
{%- endfor -%}
{%- endfor -%}
{%- endif -%}
)
{% endif %}
{% for img in images %}
.. figure:: {{ build_dir }}/{{ img.basename }}.png
{% for option in options -%}
{{ option }}
{% endfor %}
{% if html_show_formats and multi_image -%}
(
{%- for fmt in img.formats -%}
{%- if not loop.first -%}, {% endif -%}
`{{ fmt }} <{{ dest_dir }}/{{ img.basename }}.{{ fmt }}>`__
{%- endfor -%}
)
{%- endif -%}
{{ caption }}
{% endfor %}
{{ only_latex }}
{% for img in images %}
{% if 'pdf' in img.formats -%}
.. image:: {{ build_dir }}/{{ img.basename }}.pdf
{% endif -%}
{% endfor %}
{{ only_texinfo }}
{% for img in images %}
.. image:: {{ build_dir }}/{{ img.basename }}.png
{% for option in options -%}
{{ option }}
{% endfor %}
{% endfor %}
"""
exception_template = """
.. htmlonly::
[`source code <%(linkdir)s/%(basename)s.py>`__]
Exception occurred rendering plot.
"""
# the context of the plot for all directives specified with the
# :context: option
plot_context = dict()
class ImageFile(object):
def __init__(self, basename, dirname):
self.basename = basename
self.dirname = dirname
self.formats = []
def filename(self, format):
return os.path.join(self.dirname, "%s.%s" % (self.basename, format))
def filenames(self):
return [self.filename(fmt) for fmt in self.formats]
def out_of_date(original, derived):
"""
Returns True if derivative is out-of-date wrt original,
both of which are full file paths.
"""
return (not os.path.exists(derived) or
(os.path.exists(original) and
os.stat(derived).st_mtime < os.stat(original).st_mtime))
class PlotError(RuntimeError):
pass
def run_code(code, code_path, ns=None, function_name=None):
"""
Import a Python module from a path, and run the function given by
name, if function_name is not None.
"""
# Change the working directory to the directory of the example, so
# it can get at its data files, if any. Add its path to sys.path
# so it can import any helper modules sitting beside it.
if six.PY2:
pwd = os.getcwdu()
else:
pwd = os.getcwd()
old_sys_path = list(sys.path)
if setup.config.plot_working_directory is not None:
try:
os.chdir(setup.config.plot_working_directory)
except OSError as err:
raise OSError(str(err) + '\n`plot_working_directory` option in'
'Sphinx configuration file must be a valid '
'directory path')
except TypeError as err:
raise TypeError(str(err) + '\n`plot_working_directory` option in '
'Sphinx configuration file must be a string or '
'None')
sys.path.insert(0, setup.config.plot_working_directory)
elif code_path is not None:
dirname = os.path.abspath(os.path.dirname(code_path))
os.chdir(dirname)
sys.path.insert(0, dirname)
# Reset sys.argv
old_sys_argv = sys.argv
sys.argv = [code_path]
# Redirect stdout
stdout = sys.stdout
if six.PY3:
sys.stdout = io.StringIO()
else:
sys.stdout = cStringIO.StringIO()
# Assign a do-nothing print function to the namespace. There
# doesn't seem to be any other way to provide a way to (not) print
# that works correctly across Python 2 and 3.
def _dummy_print(*arg, **kwarg):
pass
try:
try:
code = unescape_doctest(code)
if ns is None:
ns = {}
if not ns:
if setup.config.plot_pre_code is None:
six.exec_(six.text_type("import numpy as np\n" +
"from matplotlib import pyplot as plt\n"), ns)
else:
six.exec_(six.text_type(setup.config.plot_pre_code), ns)
ns['print'] = _dummy_print
if "__main__" in code:
six.exec_("__name__ = '__main__'", ns)
code = remove_coding(code)
six.exec_(code, ns)
if function_name is not None:
six.exec_(function_name + "()", ns)
except (Exception, SystemExit) as err:
raise PlotError(traceback.format_exc())
finally:
os.chdir(pwd)
sys.argv = old_sys_argv
sys.path[:] = old_sys_path
sys.stdout = stdout
return ns
def clear_state(plot_rcparams, close=True):
if close:
plt.close('all')
matplotlib.rc_file_defaults()
matplotlib.rcParams.update(plot_rcparams)
def render_figures(code, code_path, output_dir, output_base, context,
function_name, config, context_reset=False,
close_figs=False):
"""
Run a pyplot script and save the low and high res PNGs and a PDF
in *output_dir*.
Save the images under *output_dir* with file names derived from
*output_base*
"""
# -- Parse format list
default_dpi = {'png': 80, 'hires.png': 200, 'pdf': 200}
formats = []
plot_formats = config.plot_formats
if isinstance(plot_formats, six.string_types):
# String Sphinx < 1.3, Split on , to mimic
# Sphinx 1.3 and later. Sphinx 1.3 always
# returns a list.
plot_formats = plot_formats.split(',')
for fmt in plot_formats:
if isinstance(fmt, six.string_types):
if ':' in fmt:
suffix,dpi = fmt.split(':')
formats.append((str(suffix), int(dpi)))
else:
formats.append((fmt, default_dpi.get(fmt, 80)))
elif type(fmt) in (tuple, list) and len(fmt)==2:
formats.append((str(fmt[0]), int(fmt[1])))
else:
raise PlotError('invalid image format "%r" in plot_formats' % fmt)
# -- Try to determine if all images already exist
code_pieces = split_code_at_show(code)
# Look for single-figure output files first
all_exists = True
img = ImageFile(output_base, output_dir)
for format, dpi in formats:
if out_of_date(code_path, img.filename(format)):
all_exists = False
break
img.formats.append(format)
if all_exists:
return [(code, [img])]
# Then look for multi-figure output files
results = []
all_exists = True
for i, code_piece in enumerate(code_pieces):
images = []
for j in xrange(1000):
if len(code_pieces) > 1:
img = ImageFile('%s_%02d_%02d' % (output_base, i, j), output_dir)
else:
img = ImageFile('%s_%02d' % (output_base, j), output_dir)
for format, dpi in formats:
if out_of_date(code_path, img.filename(format)):
all_exists = False
break
img.formats.append(format)
# assume that if we have one, we have them all
if not all_exists:
all_exists = (j > 0)
break
images.append(img)
if not all_exists:
break
results.append((code_piece, images))
if all_exists:
return results
# We didn't find the files, so build them
results = []
if context:
ns = plot_context
else:
ns = {}
if context_reset:
clear_state(config.plot_rcparams)
plot_context.clear()
close_figs = not context or close_figs
for i, code_piece in enumerate(code_pieces):
if not context or config.plot_apply_rcparams:
clear_state(config.plot_rcparams, close_figs)
elif close_figs:
plt.close('all')
run_code(code_piece, code_path, ns, function_name)
images = []
fig_managers = _pylab_helpers.Gcf.get_all_fig_managers()
for j, figman in enumerate(fig_managers):
if len(fig_managers) == 1 and len(code_pieces) == 1:
img = ImageFile(output_base, output_dir)
elif len(code_pieces) == 1:
img = ImageFile("%s_%02d" % (output_base, j), output_dir)
else:
img = ImageFile("%s_%02d_%02d" % (output_base, i, j),
output_dir)
images.append(img)
for format, dpi in formats:
try:
figman.canvas.figure.tight_layout()
figman.canvas.figure.savefig(img.filename(format),
dpi=dpi,
bbox_inches='tight')
except Exception as err:
raise PlotError(traceback.format_exc())
img.formats.append(format)
results.append((code_piece, images))
if not context or config.plot_apply_rcparams:
clear_state(config.plot_rcparams, close=not context)
return results
def run(arguments, content, options, state_machine, state, lineno):
# The user may provide a filename *or* Python code content, but not both
if arguments and content:
raise RuntimeError("plot:: directive can't have both args and content")
document = state_machine.document
config = document.settings.env.config
nofigs = 'nofigs' in options
options.setdefault('include-source', config.plot_include_source)
keep_context = 'context' in options
context_opt = None if not keep_context else options['context']
rst_file = document.attributes['source']
rst_dir = os.path.dirname(rst_file)
if len(arguments):
if not config.plot_basedir:
source_file_name = os.path.join(setup.app.builder.srcdir,
directives.uri(arguments[0]))
else:
source_file_name = os.path.join(setup.confdir, config.plot_basedir,
directives.uri(arguments[0]))
# If there is content, it will be passed as a caption.
caption = '\n'.join(content)
# If the optional function name is provided, use it
if len(arguments) == 2:
function_name = arguments[1]
else:
function_name = None
with io.open(source_file_name, 'r', encoding='utf-8') as fd:
code = fd.read()
output_base = os.path.basename(source_file_name)
else:
source_file_name = rst_file
code = textwrap.dedent("\n".join(map(str, content)))
counter = document.attributes.get('_plot_counter', 0) + 1
document.attributes['_plot_counter'] = counter
base, ext = os.path.splitext(os.path.basename(source_file_name))
output_base = '%s-%d.py' % (base, counter)
function_name = None
caption = ''
base, source_ext = os.path.splitext(output_base)
if source_ext in ('.py', '.rst', '.txt'):
output_base = base
else:
source_ext = ''
# ensure that LaTeX includegraphics doesn't choke in foo.bar.pdf filenames
output_base = output_base.replace('.', '-')
# is it in doctest format?
is_doctest = contains_doctest(code)
if 'format' in options:
if options['format'] == 'python':
is_doctest = False
else:
is_doctest = True
# determine output directory name fragment
source_rel_name = relpath(source_file_name, setup.confdir)
source_rel_dir = os.path.dirname(source_rel_name)
while source_rel_dir.startswith(os.path.sep):
source_rel_dir = source_rel_dir[1:]
# build_dir: where to place output files (temporarily)
build_dir = os.path.join(os.path.dirname(setup.app.doctreedir),
'plot_directive',
source_rel_dir)
# get rid of .. in paths, also changes pathsep
# see note in Python docs for warning about symbolic links on Windows.
# need to compare source and dest paths at end
build_dir = os.path.normpath(build_dir)
if not os.path.exists(build_dir):
os.makedirs(build_dir)
# output_dir: final location in the builder's directory
dest_dir = os.path.abspath(os.path.join(setup.app.builder.outdir,
source_rel_dir))
if not os.path.exists(dest_dir):
os.makedirs(dest_dir) # no problem here for me, but just use built-ins
# how to link to files from the RST file
dest_dir_link = os.path.join(relpath(setup.confdir, rst_dir),
source_rel_dir).replace(os.path.sep, '/')
try:
build_dir_link = relpath(build_dir, rst_dir).replace(os.path.sep, '/')
except ValueError:
# on Windows, relpath raises ValueError when path and start are on
# different mounts/drives
build_dir_link = build_dir
source_link = dest_dir_link + '/' + output_base + source_ext
# make figures
try:
results = render_figures(code,
source_file_name,
build_dir,
output_base,
keep_context,
function_name,
config,
context_reset=context_opt == 'reset',
close_figs=context_opt == 'close-figs')
errors = []
except PlotError as err:
reporter = state.memo.reporter
sm = reporter.system_message(
2, "Exception occurred in plotting %s\n from %s:\n%s" % (output_base,
source_file_name, err),
line=lineno)
results = [(code, [])]
errors = [sm]
# Properly indent the caption
caption = '\n'.join(' ' + line.strip()
for line in caption.split('\n'))
# generate output restructuredtext
total_lines = []
for j, (code_piece, images) in enumerate(results):
if options['include-source']:
if is_doctest:
lines = ['']
lines += [row.rstrip() for row in code_piece.split('\n')]
else:
lines = ['.. code-block:: python', '']
lines += [' %s' % row.rstrip()
for row in code_piece.split('\n')]
source_code = "\n".join(lines)
else:
source_code = ""
if nofigs:
images = []
opts = [':%s: %s' % (key, val) for key, val in six.iteritems(options)
if key in ('alt', 'height', 'width', 'scale', 'align', 'class')]
only_html = ".. only:: html"
only_latex = ".. only:: latex"
only_texinfo = ".. only:: texinfo"
# Not-None src_link signals the need for a source link in the generated
# html
if j == 0 and config.plot_html_show_source_link:
src_link = source_link
else:
src_link = None
result = format_template(
config.plot_template or TEMPLATE,
dest_dir=dest_dir_link,
build_dir=build_dir_link,
source_link=src_link,
multi_image=len(images) > 1,
only_html=only_html,
only_latex=only_latex,
only_texinfo=only_texinfo,
options=opts,
images=images,
source_code=source_code,
html_show_formats=config.plot_html_show_formats and not nofigs,
caption=caption)
total_lines.extend(result.split("\n"))
total_lines.extend("\n")
if total_lines:
state_machine.insert_input(total_lines, source=source_file_name)
# copy image files to builder's output directory, if necessary
if not os.path.exists(dest_dir):
cbook.mkdirs(dest_dir)
for code_piece, images in results:
for img in images:
for fn in img.filenames():
destimg = os.path.join(dest_dir, os.path.basename(fn))
if fn != destimg:
shutil.copyfile(fn, destimg)
# copy script (if necessary)
target_name = os.path.join(dest_dir, output_base + source_ext)
with io.open(target_name, 'w', encoding="utf-8") as f:
if source_file_name == rst_file:
code_escaped = unescape_doctest(code)
else:
code_escaped = code
f.write(code_escaped)
return errors
================================================
FILE: setup.py
================================================
import os
from setuptools import setup
install_requires = ['matplotlib', 'numpy', 'pandas']
try:
with open('README.rst') as readme:
long_description = readme.read()
except IOError:
long_description = 'See https://pypi.python.org/pypi/calmap'
# This is quite the hack, but we don't want to import our package from here
# since that's recipe for disaster (it might have some uninstalled
# dependencies, or we might import another already installed version).
distmeta = {}
for line in open(os.path.join('calmap', '__init__.py')):
try:
field, value = (x.strip() for x in line.split('='))
except ValueError:
continue
if field == '__version_info__':
value = value.strip('[]()')
value = '.'.join(x.strip(' \'"') for x in value.split(','))
else:
value = value.strip('\'"')
distmeta[field] = value
setup(
name='calmap',
version=distmeta['__version_info__'],
description='Calendar heatmaps from Pandas time series data',
long_description=long_description,
author=distmeta['__author__'],
author_email=distmeta['__contact__'],
url=distmeta['__homepage__'],
license='MIT License',
platforms=['any'],
packages=['calmap'],
install_requires=install_requires,
classifiers=[
'Development Status :: 3 - Alpha',
'Intended Audience :: Developers',
'Intended Audience :: Science/Research',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Topic :: Scientific/Engineering']
)
================================================
FILE: tests/conftest.py
================================================
import matplotlib
matplotlib.use('agg')
================================================
FILE: tests/requirements.txt
================================================
pytest==2.8.5
pytest-mpl==0.3
================================================
FILE: tests/test_calmap.py
================================================
"""
Tests for calmap.
"""
from __future__ import unicode_literals
import numpy as np; np.random.seed(sum(map(ord, 'calmap')))
import pandas as pd
import pytest
import calmap
@pytest.fixture
def events():
"""
We create 500 events as random float values assigned to random days over a
700-day period.
"""
all_days = pd.date_range('1/15/2014', periods=700, freq='D')
days = np.random.choice(all_days, 500)
return pd.Series(np.random.randn(len(days)), index=days)
@pytest.mark.mpl_image_compare(tolerance=20)
def test_yearplot(events):
"""
By default, `yearplot` plots the first year and sums the values per day.
"""
ax = calmap.yearplot(events)
return ax.figure
@pytest.mark.mpl_image_compare(tolerance=20)
def test_yearplot_year(events):
"""
We can choose which year is plotted with the `year` keyword argment.
"""
ax = calmap.yearplot(events, year=2015)
return ax.figure
@pytest.mark.mpl_image_compare(tolerance=20)
def test_yearplot_cmap_fillcolor_linewidth(events):
"""
The appearance can be changed by using another colormap. Here we also use
a darker fill color for days without data and remove the lines.
"""
ax = calmap.yearplot(events, cmap='YlGn', fillcolor='grey', linewidth=0)
return ax.figure
@pytest.mark.mpl_image_compare(tolerance=20)
def test_yearplot_monthticks_daylabels_dayticks(events):
"""
We can ask to draw only every nth label, or explicitely supply the label
indices. The labels themselves can also be customized.
"""
ax = calmap.yearplot(events, monthticks=3, daylabels='MTWTFSS',
dayticks=[0, 2, 4, 6])
return ax.figure
@pytest.mark.mpl_image_compare(tolerance=50)
def test_calendarplot(events):
"""
With `calendarplot` we can plot several years in one figure.
"""
fig, axes = calmap.calendarplot(events)
return fig
gitextract_ftj_oh14/
├── .gitignore
├── .travis.yml
├── AUTHORS.rst
├── LICENSE.rst
├── MANIFEST.in
├── README.rst
├── calmap/
│ └── __init__.py
├── doc/
│ ├── .gitignore
│ ├── Makefile
│ ├── conf.py
│ ├── index.rst
│ ├── requirements.txt
│ └── sphinxext/
│ └── plot_directive.py
├── setup.py
└── tests/
├── conftest.py
├── requirements.txt
└── test_calmap.py
SYMBOL INDEX (31 symbols across 3 files)
FILE: calmap/__init__.py
function yearplot (line 32) | def yearplot(data, year=None, how='sum', vmin=None, vmax=None, cmap='Reds',
function calendarplot (line 237) | def calendarplot(data, how='sum', yearlabels=True, yearascending=True, y...
FILE: doc/sphinxext/plot_directive.py
function format_template (line 161) | def format_template(template, **kw):
function format_template (line 165) | def format_template(template, **kw):
function plot_directive (line 187) | def plot_directive(name, arguments, options, content, lineno,
function _option_boolean (line 193) | def _option_boolean(arg):
function _option_context (line 205) | def _option_context(arg):
function _option_format (line 211) | def _option_format(arg):
function _option_align (line 215) | def _option_align(arg):
function mark_plot_labels (line 220) | def mark_plot_labels(app, document):
function setup (line 251) | def setup(app):
function contains_doctest (line 287) | def contains_doctest(text):
function unescape_doctest (line 299) | def unescape_doctest(text):
function split_code_at_show (line 320) | def split_code_at_show(text):
function remove_coding (line 343) | def remove_coding(text):
class ImageFile (line 427) | class ImageFile(object):
method __init__ (line 428) | def __init__(self, basename, dirname):
method filename (line 433) | def filename(self, format):
method filenames (line 436) | def filenames(self):
function out_of_date (line 440) | def out_of_date(original, derived):
class PlotError (line 450) | class PlotError(RuntimeError):
function run_code (line 454) | def run_code(code, code_path, ns=None, function_name=None):
function clear_state (line 530) | def clear_state(plot_rcparams, close=True):
function render_figures (line 537) | def render_figures(code, code_path, output_dir, output_base, context,
function run (line 664) | def run(arguments, content, options, state_machine, state, lineno):
FILE: tests/test_calmap.py
function events (line 16) | def events():
function test_yearplot (line 27) | def test_yearplot(events):
function test_yearplot_year (line 36) | def test_yearplot_year(events):
function test_yearplot_cmap_fillcolor_linewidth (line 45) | def test_yearplot_cmap_fillcolor_linewidth(events):
function test_yearplot_monthticks_daylabels_dayticks (line 55) | def test_yearplot_monthticks_daylabels_dayticks(events):
function test_calendarplot (line 66) | def test_calendarplot(events):
Condensed preview — 17 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (70K chars).
[
{
"path": ".gitignore",
"chars": 35,
"preview": "*.pyc\nbuild\ndist\n.cache\n*.egg-info\n"
},
{
"path": ".travis.yml",
"chars": 1190,
"preview": "# Validate this file using http://lint.travis-ci.org/\nlanguage: python\nsudo: false\ncache: pip\npython:\n - \"2.7\"\n - \"3.4"
},
{
"path": "AUTHORS.rst",
"chars": 95,
"preview": "Calmap is written and maintained by Martijn Vermaat.\n\n- Martijn Vermaat <martijn@vermaat.name>\n"
},
{
"path": "LICENSE.rst",
"chars": 1110,
"preview": "Copyright (c) 2015 by Martijn Vermaat and contributors (see AUTHORS.rst\nfor details).\n\nPermission is hereby granted, fre"
},
{
"path": "MANIFEST.in",
"chars": 55,
"preview": "include AUTHORS.rst CHANGES.rst LICENSE.rst README.rst\n"
},
{
"path": "README.rst",
"chars": 779,
"preview": "| **Note:** See `MarvinT/calmap <https://github.com/MarvinT/calmap/>`_ for the maintained version of the project. That i"
},
{
"path": "calmap/__init__.py",
"chars": 11412,
"preview": "\"\"\"\nCalendar heatmaps from Pandas time series data.\n\nPlot Pandas time series data sampled by day in a heatmap per calend"
},
{
"path": "doc/.gitignore",
"chars": 7,
"preview": ".build\n"
},
{
"path": "doc/Makefile",
"chars": 7409,
"preview": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS =\nSPHINXBUILD "
},
{
"path": "doc/conf.py",
"chars": 9827,
"preview": "# -*- coding: utf-8 -*-\n#\n# Calmap documentation build configuration file, created by\n# sphinx-quickstart on Thu Nov 26 "
},
{
"path": "doc/index.rst",
"chars": 2207,
"preview": ".. currentmodule:: calmap\n\n\nCalendar heatmaps from Pandas time series data\n============================================="
},
{
"path": "doc/requirements.txt",
"chars": 34,
"preview": "matplotlib>=1.5.0\nnumpydoc\nSphinx\n"
},
{
"path": "doc/sphinxext/plot_directive.py",
"chars": 28496,
"preview": "\"\"\"\nA directive for including a matplotlib plot in a Sphinx document.\n\nBy default, in HTML output, `plot` will include a"
},
{
"path": "setup.py",
"chars": 1794,
"preview": "import os\nfrom setuptools import setup\n\ninstall_requires = ['matplotlib', 'numpy', 'pandas']\n\ntry:\n with open('README"
},
{
"path": "tests/conftest.py",
"chars": 41,
"preview": "import matplotlib\n\nmatplotlib.use('agg')\n"
},
{
"path": "tests/requirements.txt",
"chars": 30,
"preview": "pytest==2.8.5\npytest-mpl==0.3\n"
},
{
"path": "tests/test_calmap.py",
"chars": 1918,
"preview": "\"\"\"\nTests for calmap.\n\"\"\"\n\n\nfrom __future__ import unicode_literals\n\nimport numpy as np; np.random.seed(sum(map(ord, 'ca"
}
]
About this extraction
This page contains the full source code of the martijnvermaat/calmap GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 17 files (64.9 KB), approximately 17.1k tokens, and a symbol index with 31 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.