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 ================================================ 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 `_ 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 `_ time series data sampled by day in a heatmap per calendar year, similar to GitHub's contributions plot, using `matplotlib `_. .. image:: https://pythonhosted.org/calmap/_images/index-2.png :alt: Example calendar heatmap Usage ----- See the `documentation `_. 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 ' where 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 # " v 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 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 `_ time series data sampled by day in a heatmap per calendar year, similar to GitHub's contributions plot, using `matplotlib `_. 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 ` 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 `_. .. _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, '', '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