Commit 43e910f0 authored by Michael Droettboom's avatar Michael Droettboom

Support downloading of plot files

parent 77266efd
...@@ -191,7 +191,6 @@ root/.built: \ ...@@ -191,7 +191,6 @@ root/.built: \
( \ ( \
cd root/lib/python$(PYMINOR); \ cd root/lib/python$(PYMINOR); \
rm -fr `cat ../../../remove_modules.txt`; \ rm -fr `cat ../../../remove_modules.txt`; \
rm encodings/cp*.py; \
rm encodings/mac_*.py; \ rm encodings/mac_*.py; \
find . -name "*.wasm.pre" -type f -delete ; \ find . -name "*.wasm.pre" -type f -delete ; \
find -type d -name __pycache__ -prune -exec rm -rf {} \; \ find -type d -name __pycache__ -prune -exec rm -rf {} \; \
......
diff --git a/src/_path_wrapper.cpp b/src/_path_wrapper.cpp
index 354097557..2fd90adb9 100644
--- a/src/_path_wrapper.cpp
+++ b/src/_path_wrapper.cpp
@@ -437,23 +437,25 @@ static PyObject *Py_affine_transform(PyObject *self, PyObject *args, PyObject *k
return NULL;
}
- try {
- numpy::array_view<double, 2> vertices(vertices_obj);
+ PyArrayObject* vertices_arr = (PyArrayObject *)PyArray_ContiguousFromAny(vertices_obj, NPY_DOUBLE, 1, 2);
+ if (vertices_arr == NULL) {
+ return NULL;
+ }
+
+ if (PyArray_NDIM(vertices_arr) == 2) {
+ numpy::array_view<double, 2> vertices(vertices_arr);
npy_intp dims[] = { (npy_intp)vertices.size(), 2 };
numpy::array_view<double, 2> result(dims);
CALL_CPP("affine_transform", (affine_transform_2d(vertices, trans, result)));
+ Py_DECREF(vertices_arr);
+ return result.pyobj();
+ } else { // PyArray_NDIM(vertices_arr) == 1
+ numpy::array_view<double, 1> vertices(vertices_arr);
+ npy_intp dims[] = { (npy_intp)vertices.size() };
+ numpy::array_view<double, 1> result(dims);
+ CALL_CPP("affine_transform", (affine_transform_1d(vertices, trans, result)));
+ Py_DECREF(vertices_arr);
return result.pyobj();
- } catch (py::exception &) {
- PyErr_Clear();
- try {
- numpy::array_view<double, 1> vertices(vertices_obj);
- npy_intp dims[] = { (npy_intp)vertices.size() };
- numpy::array_view<double, 1> result(dims);
- CALL_CPP("affine_transform", (affine_transform_1d(vertices, trans, result)));
- return result.pyobj();
- } catch (py::exception &) {
- return NULL;
- }
}
}
diff --git a/src/numpy_cpp.h b/src/numpy_cpp.h
index 2218078ae..cddbcb659 100644
--- a/src/numpy_cpp.h
+++ b/src/numpy_cpp.h
@@ -398,6 +398,15 @@ class array_view : public detail::array_view_accessors<array_view, T, ND>
m_strides = strides;
}
+ array_view(PyArrayObject *arr)
+ {
+ m_arr = arr;
+ Py_XINCREF(arr);
+ m_shape = PyArray_DIMS(m_arr);
+ m_strides = PyArray_STRIDES(m_arr);
+ m_data = (char *)PyArray_BYTES(m_arr);
+ }
+
array_view(npy_intp shape[ND]) : m_arr(NULL), m_shape(NULL), m_strides(NULL), m_data(NULL)
{
PyObject *arr = PyArray_SimpleNew(ND, shape, type_num_of<T>::value);
@@ -456,18 +465,18 @@ class array_view : public detail::array_view_accessors<array_view, T, ND>
m_data = NULL;
m_shape = zeros;
m_strides = zeros;
- if (PyArray_NDIM(tmp) == 0 && ND == 0) {
- m_arr = tmp;
- return 1;
- }
+ if (PyArray_NDIM(tmp) == 0 && ND == 0) {
+ m_arr = tmp;
+ return 1;
+ }
}
- if (PyArray_NDIM(tmp) != ND) {
- PyErr_Format(PyExc_ValueError,
- "Expected %d-dimensional array, got %d",
- ND,
- PyArray_NDIM(tmp));
- Py_DECREF(tmp);
- return 0;
+ if (PyArray_NDIM(tmp) != ND) {
+ PyErr_Format(PyExc_ValueError,
+ "Expected %d-dimensional array, got %d",
+ ND,
+ PyArray_NDIM(tmp));
+ Py_DECREF(tmp);
+ return 0;
}
/* Copy some of the data to the view object for faster access */
diff --git a/src/py_converters.cpp b/src/py_converters.cpp
index cc235df2e..9119df6b6 100644
--- a/src/py_converters.cpp
+++ b/src/py_converters.cpp
@@ -158,45 +158,36 @@ int convert_rect(PyObject *rectobj, void *rectp)
rect->x2 = 0.0;
rect->y2 = 0.0;
} else {
- try
- {
- numpy::array_view<const double, 2> rect_arr(rectobj);
+ PyArrayObject *rect_arr = (PyArrayObject *)PyArray_ContiguousFromAny(
+ rectobj, NPY_DOUBLE, 1, 2);
+ if (rect_arr == NULL) {
+ return 0;
+ }
- if (rect_arr.dim(0) != 2 || rect_arr.dim(1) != 2) {
+ if (PyArray_NDIM(rect_arr) == 2) {
+ if (PyArray_DIM(rect_arr, 0) != 2 ||
+ PyArray_DIM(rect_arr, 1) != 2) {
PyErr_SetString(PyExc_ValueError, "Invalid bounding box");
+ Py_DECREF(rect_arr);
return 0;
}
- rect->x1 = rect_arr(0, 0);
- rect->y1 = rect_arr(0, 1);
- rect->x2 = rect_arr(1, 0);
- rect->y2 = rect_arr(1, 1);
- }
- catch (py::exception &)
- {
- PyErr_Clear();
-
- try
- {
- numpy::array_view<const double, 1> rect_arr(rectobj);
-
- if (rect_arr.dim(0) != 4) {
- PyErr_SetString(PyExc_ValueError, "Invalid bounding box");
- return 0;
- }
-
- rect->x1 = rect_arr(0);
- rect->y1 = rect_arr(1);
- rect->x2 = rect_arr(2);
- rect->y2 = rect_arr(3);
- }
- catch (py::exception &)
- {
+ } else { // PyArray_NDIM(rect_arr) == 1
+ if (PyArray_DIM(rect_arr, 0) != 4) {
+ PyErr_SetString(PyExc_ValueError, "Invalid bounding box");
+ Py_DECREF(rect_arr);
return 0;
}
}
- }
+ double *buff = (double *)PyArray_DATA(rect_arr);
+ rect->x1 = buff[0];
+ rect->y1 = buff[1];
+ rect->x2 = buff[2];
+ rect->y2 = buff[3];
+
+ Py_DECREF(rect_arr);
+ }
return 1;
}
@@ -336,27 +327,26 @@ int convert_trans_affine(PyObject *obj, void *transp)
return 1;
}
- try
- {
- numpy::array_view<const double, 2> matrix(obj);
+ PyArrayObject *array = (PyArrayObject *)PyArray_ContiguousFromAny(obj, NPY_DOUBLE, 2, 2);
+ if (array == NULL) {
+ return 0;
+ }
- if (matrix.dim(0) == 3 && matrix.dim(1) == 3) {
- trans->sx = matrix(0, 0);
- trans->shx = matrix(0, 1);
- trans->tx = matrix(0, 2);
+ if (PyArray_DIM(array, 0) == 3 && PyArray_DIM(array, 1) == 3) {
+ double *buffer = (double *)PyArray_DATA(array);
+ trans->sx = buffer[0];
+ trans->shx = buffer[1];
+ trans->tx = buffer[2];
- trans->shy = matrix(1, 0);
- trans->sy = matrix(1, 1);
- trans->ty = matrix(1, 2);
+ trans->shy = buffer[3];
+ trans->sy = buffer[4];
+ trans->ty = buffer[5];
- return 1;
- }
- }
- catch (py::exception &)
- {
- return 0;
+ Py_DECREF(array);
+ return 1;
}
+ Py_DECREF(array);
PyErr_SetString(PyExc_ValueError, "Invalid affine transformation matrix");
return 0;
}
import base64
import io
from matplotlib.backends import backend_agg from matplotlib.backends import backend_agg
from matplotlib.backend_bases import _Backend from matplotlib.backend_bases import _Backend
from matplotlib import backend_bases, _png from matplotlib import backend_bases, _png
...@@ -6,7 +9,6 @@ from js import iodide ...@@ -6,7 +9,6 @@ from js import iodide
from js import document from js import document
from js import window from js import window
from js import ImageData from js import ImageData
from js import Uint8ClampedArray
class FigureCanvasWasm(backend_agg.FigureCanvasAgg): class FigureCanvasWasm(backend_agg.FigureCanvasAgg):
...@@ -167,7 +169,7 @@ _FONTAWESOME_ICONS = { ...@@ -167,7 +169,7 @@ _FONTAWESOME_ICONS = {
'forward': 'fa-arrow-right', 'forward': 'fa-arrow-right',
'zoom_to_rect': 'fa-search-plus', 'zoom_to_rect': 'fa-search-plus',
'move': 'fa-arrows', 'move': 'fa-arrows',
'download': 'download', 'download': 'fa-download',
None: None, None: None,
} }
...@@ -178,21 +180,57 @@ class NavigationToolbar2Wasm(backend_bases.NavigationToolbar2): ...@@ -178,21 +180,57 @@ class NavigationToolbar2Wasm(backend_bases.NavigationToolbar2):
def get_element(self): def get_element(self):
div = document.createElement('span') div = document.createElement('span')
def add_spacer():
span = document.createElement('span')
span.style.minWidth = '16px'
span.textContent = '\u00a0'
div.appendChild(span)
for text, tooltip_text, image_file, name_of_method in self.toolitems: for text, tooltip_text, image_file, name_of_method in self.toolitems:
if image_file in _FONTAWESOME_ICONS: if image_file in _FONTAWESOME_ICONS:
if image_file is None: if image_file is None:
span = document.createElement('span') add_spacer()
span.style.minWidth = 16
span.style.textContent = ' '
div.appendChild(span)
else: else:
button = document.createElement('button') button = document.createElement('button')
button.classList.add('fa') button.classList.add('fa')
button.classList.add(_FONTAWESOME_ICONS[image_file]) button.classList.add(_FONTAWESOME_ICONS[image_file])
button.addEventListener('click', getattr(self, name_of_method)) button.addEventListener('click', getattr(self, name_of_method))
div.appendChild(button) div.appendChild(button)
for filetype in ('png', 'svg', 'pdf'):
button = document.createElement('button')
button.classList.add('fa')
button.textContent = filetype
button.addEventListener('click', getattr(self, 'download_' + filetype))
div.appendChild(button)
return div return div
def download_png(self, event):
self.download('png', 'image/png')
def download_svg(self, event):
self.download('svg', 'image/svg+xml')
def download_pdf(self, event):
self.download('pdf', 'application/pdf')
def download(self, format, mimetype):
element = document.createElement('a')
data = io.BytesIO()
try:
self.canvas.figure.savefig(data, format=format)
except Exception as e:
raise
element.setAttribute('href', 'data:{};base64,{}'.format(
mimetype, base64.b64encode(data.getvalue()).decode('ascii')))
element.setAttribute('download', 'plot.{}'.format(format))
element.style.display = 'none'
document.body.appendChild(element)
element.click()
document.body.removeChild(element)
def set_message(self, message): def set_message(self, message):
self.canvas.set_message(message) self.canvas.set_message(message)
......
...@@ -7,3 +7,15 @@ def test_matplotlib(selenium): ...@@ -7,3 +7,15 @@ def test_matplotlib(selenium):
) )
assert img.startswith('data:image/png;base64,') assert img.startswith('data:image/png;base64,')
assert len(img) == 26766 assert len(img) == 26766
def test_svg(selenium):
selenium.load_package("matplotlib")
selenium.run("from matplotlib import pyplot as plt")
selenium.run("x = plt.plot([1,2,3])")
selenium.run("import io")
selenium.run("fd = io.BytesIO()")
selenium.run("plt.savefig(fd, format='svg')")
content = selenium.run("fd.getvalue().decode('utf8')")
assert len(content) == 15752
assert content.startswith("<?xml")
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment