Skip to content

Commit 672dea6

Browse files
committedApr 24, 2015
New matplotlib-based plot directive
1 parent c104847 commit 672dea6

File tree

4 files changed

+128
-0
lines changed

4 files changed

+128
-0
lines changed
 

‎v7/pyplots/README.md

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
A quick & dirty reimplementation of the [plot directive](http://matplotlib.org/api/pyplot_api.html#module-matplotlib.pyplot) useful for doing nice plots, and for sphinx compatibility.
2+
3+
**IMPORTANT:** this directive executes arbitrary python code passed as the argument, so it's wildly
4+
insecure. Do not enable this plugin if you are ever building a site with untrusted content.
5+
6+
Differences with the original:
7+
8+
* Context is always reset between plots, the context option *will* produce an error.
9+
* No configuration options whatsoever.
10+
* It always uses SVG because it's 2015
11+
* the ``include-source`` option is supported but completely ignored
12+
13+
**NOTE:** if you use code inside the directive instead of files, every time you edit it it will
14+
produce different random-named images in ``output/pyplots``. That's probably worth cleaning every once
15+
in a while.

‎v7/pyplots/pyplots.plugin

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[Core]
2+
Name = pyplots
3+
Module = pyplots
4+
5+
[Documentation]
6+
Author = Roberto Alsina
7+
Version = 0.1
8+
Website = http://plugins.getnikola.com#pyplots
9+
Description = Compatibility with matplotlib's pyplots directive for sphinx
10+

‎v7/pyplots/pyplots.py

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# Copyright © 2012-2015 Roberto Alsina and others.
4+
5+
# Permission is hereby granted, free of charge, to any
6+
# person obtaining a copy of this software and associated
7+
# documentation files (the "Software"), to deal in the
8+
# Software without restriction, including without limitation
9+
# the rights to use, copy, modify, merge, publish,
10+
# distribute, sublicense, and/or sell copies of the
11+
# Software, and to permit persons to whom the Software is
12+
# furnished to do so, subject to the following conditions:
13+
#
14+
# The above copyright notice and this permission notice
15+
# shall be included in all copies or substantial portions of
16+
# the Software.
17+
#
18+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
19+
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
20+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
21+
# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
22+
# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
23+
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
24+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26+
27+
from hashlib import md5
28+
import io
29+
import os
30+
31+
from docutils import nodes
32+
from docutils.parsers.rst import directives
33+
try:
34+
import matplotlib
35+
import matplotlib._pylab_helpers
36+
import matplotlib.pyplot as plt
37+
except ImportError:
38+
matplotlib = None
39+
40+
from nikola.plugin_categories import RestExtension
41+
from nikola.utils import req_missing, makedirs
42+
43+
_site = None
44+
45+
46+
class Plugin(RestExtension):
47+
48+
name = "pyplots"
49+
50+
def set_site(self, site):
51+
global _site
52+
_site = self.site = site
53+
directives.register_directive('plot', PyPlot)
54+
PyPlot.out_dir = os.path.join(site.config['OUTPUT_FOLDER'], 'pyplots')
55+
return super(Plugin, self).set_site(site)
56+
57+
pyplot_spec = directives.images.Image.option_spec
58+
pyplot_spec['include-source'] = directives.flag
59+
60+
61+
class PyPlot(directives.images.Image):
62+
""" Reimplementation of http://matplotlib.org/sampledoc/extensions.html#inserting-matplotlib-plots."""
63+
64+
has_content = True
65+
option_spec = pyplot_spec
66+
optional_arguments = 1
67+
required_arguments = 0
68+
69+
def run(self):
70+
if matplotlib is None:
71+
msg = req_missing(['matplotlib'], 'use the plot directive', optional=True)
72+
return [nodes.raw('', '<div class="text-error">{0}</div>'.format(msg), format='html')]
73+
74+
if not self.arguments and not self.content:
75+
raise self.error('The plot directive needs either an argument or content.')
76+
77+
if self.arguments and self.content:
78+
raise self.error('The plot directive needs either an argument or content, not both.')
79+
80+
if self.arguments:
81+
plot_path = self.arguments[0]
82+
with io.open(plot_path, encoding='utf-8') as fd:
83+
data = fd.read()
84+
elif self.content:
85+
data = '\n'.join(self.content)
86+
plot_path = md5(data).hexdigest()
87+
88+
# Always reset context
89+
plt.close('all')
90+
matplotlib.rc_file_defaults()
91+
# Run plot
92+
exec(data)
93+
94+
out_path = os.path.join(self.out_dir, plot_path + '.svg')
95+
plot_url = '/' + os.path.join('pyplots', plot_path + '.svg').replace(os.sep, '/')
96+
97+
figures = [manager.canvas.figure for manager in matplotlib._pylab_helpers.Gcf.get_all_fig_managers()]
98+
for figure in figures:
99+
makedirs(os.path.dirname(out_path))
100+
figure.savefig(out_path, format='svg') # Yes, if there's more than one, it's overwritten, sucks.
101+
self.arguments = [plot_url]
102+
return super(PyPlot, self).run()

‎v7/pyplots/requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
matplotlib

0 commit comments

Comments
 (0)
Please sign in to comment.