Skip to content

Sync up #1

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
May 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
language: minimal
dist: trusty
services:
- docker
script:
- make -C contrib docker_build
15 changes: 9 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,24 @@
CXXFLAGS += -std=c++11 -Wno-conversion

# Default to using system's default version of python
PYTHON_BIN ?= python
PYTHON_BIN ?= python3
PYTHON_CONFIG := $(PYTHON_BIN)-config
PYTHON_INCLUDE ?= $(shell $(PYTHON_CONFIG) --includes)
EXTRA_FLAGS := $(PYTHON_INCLUDE)
LDFLAGS += $(shell $(PYTHON_CONFIG) --libs)
# NOTE: Since python3.8, the correct invocation is `python3-config --libs --embed`.
# So of course the proper way to get python libs for embedding now is to
# invoke that, check if it crashes, and fall back to just `--libs` if it does.
LDFLAGS += $(shell if $(PYTHON_CONFIG) --libs --embed >/dev/null; then $(PYTHON_CONFIG) --libs --embed; else $(PYTHON_CONFIG) --libs; fi)

# Either finds numpy or set -DWITHOUT_NUMPY
EXTRA_FLAGS += $(shell $(PYTHON_BIN) $(CURDIR)/numpy_flags.py)
WITHOUT_NUMPY := $(findstring $(CXXFLAGS), WITHOUT_NUMPY)
WITHOUT_NUMPY := $(findstring $(EXTRA_FLAGS), WITHOUT_NUMPY)

# Examples requiring numpy support to compile
EXAMPLES_NUMPY := surface
EXAMPLES_NUMPY := surface colorbar
EXAMPLES := minimal basic modern animation nonblock xkcd quiver bar \
fill_inbetween fill update subplot2grid colorbar lines3d \
$(if WITHOUT_NUMPY,,$(EXAMPLES_NUMPY))
fill_inbetween fill update subplot2grid lines3d \
$(if $(WITHOUT_NUMPY),,$(EXAMPLES_NUMPY))

# Prefix every example with 'examples/build/'
EXAMPLE_TARGETS := $(patsubst %,examples/build/%,$(EXAMPLES))
Expand Down
43 changes: 30 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ A more comprehensive example:

namespace plt = matplotlibcpp;

int main()
int main()
{
// Prepare data.
int n = 5000;
Expand Down Expand Up @@ -73,26 +73,26 @@ Alternatively, matplotlib-cpp also supports some C++11-powered syntactic sugar:
using namespace std;
namespace plt = matplotlibcpp;

int main()
{
int main()
{
// Prepare data.
int n = 5000; // number of data points
vector<double> x(n),y(n);
vector<double> x(n),y(n);
for(int i=0; i<n; ++i) {
double t = 2*M_PI*i/n;
x.at(i) = 16*sin(t)*sin(t)*sin(t);
y.at(i) = 13*cos(t) - 5*cos(2*t) - 2*cos(3*t) - cos(4*t);
}

// plot() takes an arbitrary number of (x,y,format)-triples.
// plot() takes an arbitrary number of (x,y,format)-triples.
// x must be iterable (that is, anything providing begin(x) and end(x)),
// y must either be callable (providing operator() const) or iterable.
// y must either be callable (providing operator() const) or iterable.
plt::plot(x, y, "r-", x, [](double d) { return 12.5+abs(sin(d)); }, "k-");


// show plots
plt::show();
}
}
```
g++ modern.cpp -std=c++11 -I/usr/include/python2.7 -lpython

Expand Down Expand Up @@ -199,13 +199,16 @@ On Ubuntu:
sudo apt-get install python-matplotlib python-numpy python2.7-dev

If, for some reason, you're unable to get a working installation of numpy on your system,
you can add the define `WITHOUT_NUMPY` to erase this dependency.
you can define the macro `WITHOUT_NUMPY` before including the header file to erase this
dependency.

The C++-part of the library consists of the single header file `matplotlibcpp.h` which can be placed
anywhere.

Since a python interpreter is opened internally, it is necessary to link against `libpython2.7` in order to use
matplotlib-cpp.
Since a python interpreter is opened internally, it is necessary to link against `libpython` in order
to user matplotlib-cpp. Most versions should work, although `libpython2.7` and `libpython3.6` are
probably the most regularly testedr.


# CMake

Expand All @@ -232,6 +235,20 @@ target_include_directories(myproject PRIVATE ${PYTHON_INCLUDE_DIRS})
target_link_libraries(myproject ${PYTHON_LIBRARIES})
```


# Vcpkg

You can download and install matplotlib-cpp using the [vcpkg](https://github.com/Microsoft/vcpkg) dependency manager:

git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
./vcpkg integrate install
vcpkg install matplotlib-cpp

The matplotlib-cpp port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository.


# C++11

Currently, c++11 is required to build matplotlib-cpp. The last working commit that did
Expand All @@ -256,10 +273,10 @@ The same technique can be used for linking against a custom build of python

Why?
----
I initially started this library during my diploma thesis. The usual approach of
I initially started this library during my diploma thesis. The usual approach of
writing data from the c++ algorithm to a file and afterwards parsing and plotting
it in python using matplotlib proved insufficient: Keeping the algorithm
and plotting code in sync requires a lot of effort when the C++ code frequently and substantially
and plotting code in sync requires a lot of effort when the C++ code frequently and substantially
changes. Additionally, the python yaml parser was not able to cope with files that
exceed a few hundred megabytes in size.

Expand Down Expand Up @@ -290,4 +307,4 @@ Todo/Issues/Wishlist
in "".'

* MacOS: `Unable to import matplotlib.pyplot`. Cause: In mac os image rendering back end of matplotlib (what-is-a-backend to render using the API of Cocoa by default). There is Qt4Agg and GTKAgg and as a back-end is not the default. Set the back end of macosx that is differ compare with other windows or linux os.
Solution is discribed [here](https://stackoverflow.com/questions/21784641/installation-issue-with-matplotlib-python?noredirect=1&lq=1), additional information can be found there too(see links in answers).
Solution is described [here](https://stackoverflow.com/questions/21784641/installation-issue-with-matplotlib-python?noredirect=1&lq=1), additional information can be found there too(see links in answers).
27 changes: 27 additions & 0 deletions contrib/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
FROM debian:10 AS builder
RUN apt-get update \
&& apt-get install --yes --no-install-recommends \
g++ \
libpython3-dev \
make \
python3 \
python3-dev \
python3-numpy

ADD Makefile matplotlibcpp.h numpy_flags.py /opt/
ADD examples/*.cpp /opt/examples/
RUN cd /opt \
&& make PYTHON_BIN=python3 \
&& ls examples/build

FROM debian:10
RUN apt-get update \
&& apt-get install --yes --no-install-recommends \
libpython3-dev \
python3-matplotlib \
python3-numpy

COPY --from=builder /opt/examples/build /opt/
RUN cd /opt \
&& ls \
&& ./basic
6 changes: 6 additions & 0 deletions contrib/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
all: docker_build

docker_build:
cd .. && \
docker build . -f contrib/Dockerfile -t matplotlibcpp && \
cd contrib
96 changes: 64 additions & 32 deletions matplotlibcpp.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
#pragma once

// Python headers must be included before any system headers, since
// they define _POSIX_C_SOURCE
#include <Python.h>

#include <vector>
#include <map>
#include <array>
Expand All @@ -9,9 +13,6 @@
#include <iostream>
#include <cstdint> // <cstdint> requires c++11 support
#include <functional>
#include <unordered_map>

#include <Python.h>

#ifndef WITHOUT_NUMPY
# define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
Expand Down Expand Up @@ -297,25 +298,35 @@ template <> struct select_npy_type<uint16_t> { const static NPY_TYPES type = NPY
template <> struct select_npy_type<uint32_t> { const static NPY_TYPES type = NPY_ULONG; };
template <> struct select_npy_type<uint64_t> { const static NPY_TYPES type = NPY_UINT64; };

// Sanity checks; comment them out or change the numpy type below if you're compiling on
// a platform where they don't apply
static_assert(sizeof(long long) == 8);
template <> struct select_npy_type<long long> { const static NPY_TYPES type = NPY_INT64; };
static_assert(sizeof(unsigned long long) == 8);
template <> struct select_npy_type<unsigned long long> { const static NPY_TYPES type = NPY_UINT64; };
// TODO: add int, long, etc.

template<typename Numeric>
PyObject* get_array(const std::vector<Numeric>& v)
{
detail::_interpreter::get(); //interpreter needs to be initialized for the numpy commands to work
detail::_interpreter::get(); //interpreter needs to be initialized for the numpy commands to work
npy_intp vsize = v.size();
NPY_TYPES type = select_npy_type<Numeric>::type;
if (type == NPY_NOTYPE)
{
std::vector<double> vd(v.size());
npy_intp vsize = v.size();
std::copy(v.begin(),v.end(),vd.begin());
PyObject* varray = PyArray_SimpleNewFromData(1, &vsize, NPY_DOUBLE, (void*)(vd.data()));
if (type == NPY_NOTYPE) {
size_t memsize = v.size()*sizeof(double);
double* dp = static_cast<double*>(::malloc(memsize));
for (size_t i=0; i<v.size(); ++i)
dp[i] = v[i];
PyObject* varray = PyArray_SimpleNewFromData(1, &vsize, NPY_DOUBLE, dp);
PyArray_UpdateFlags(reinterpret_cast<PyArrayObject*>(varray), NPY_ARRAY_OWNDATA);
return varray;
}

npy_intp vsize = v.size();

PyObject* varray = PyArray_SimpleNewFromData(1, &vsize, type, (void*)(v.data()));
return varray;
}


template<typename Numeric>
PyObject* get_2darray(const std::vector<::std::vector<Numeric>>& v)
{
Expand All @@ -340,8 +351,22 @@ PyObject* get_2darray(const std::vector<::std::vector<Numeric>>& v)
return reinterpret_cast<PyObject *>(varray);
}

#else // fallback if we don't have numpy: copy every element of the given vector

template<typename Numeric>
PyObject* get_array(const std::vector<Numeric>& v)
{
PyObject* list = PyList_New(v.size());
for(size_t i = 0; i < v.size(); ++i) {
PyList_SetItem(list, i, PyFloat_FromDouble(v.at(i)));
}
return list;
}

#endif // WITHOUT_NUMPY

// sometimes, for labels and such, we need string arrays
PyObject * get_array(const std::vector<std::string>& strings)
inline PyObject * get_array(const std::vector<std::string>& strings)
{
PyObject* list = PyList_New(strings.size());
for (std::size_t i = 0; i < strings.size(); ++i) {
Expand All @@ -361,20 +386,6 @@ PyObject* get_listlist(const std::vector<std::vector<Numeric>>& ll)
return listlist;
}

#else // fallback if we don't have numpy: copy every element of the given vector

template<typename Numeric>
PyObject* get_array(const std::vector<Numeric>& v)
{
PyObject* list = PyList_New(v.size());
for(size_t i = 0; i < v.size(); ++i) {
PyList_SetItem(list, i, PyFloat_FromDouble(v.at(i)));
}
return list;
}

#endif // WITHOUT_NUMPY

} // namespace detail

/// Plot a line through the given x and y data points..
Expand Down Expand Up @@ -793,7 +804,7 @@ template<typename NumericX, typename NumericY>
bool scatter(const std::vector<NumericX>& x,
const std::vector<NumericY>& y,
const double s=1.0, // The marker size in points**2
const std::unordered_map<std::string, std::string> & keywords = {})
const std::map<std::string, std::string> & keywords = {})
{
assert(x.size() == y.size());

Expand Down Expand Up @@ -823,7 +834,7 @@ bool scatter(const std::vector<NumericX>& x,
template<typename Numeric>
bool boxplot(const std::vector<std::vector<Numeric>>& data,
const std::vector<std::string>& labels = {},
const std::unordered_map<std::string, std::string> & keywords = {})
const std::map<std::string, std::string> & keywords = {})
{
PyObject* listlist = detail::get_listlist(data);
PyObject* args = PyTuple_New(1);
Expand Down Expand Up @@ -854,7 +865,7 @@ bool boxplot(const std::vector<std::vector<Numeric>>& data,

template<typename Numeric>
bool boxplot(const std::vector<Numeric>& data,
const std::unordered_map<std::string, std::string> & keywords = {})
const std::map<std::string, std::string> & keywords = {})
{
PyObject* vector = detail::get_array(data);
PyObject* args = PyTuple_New(1);
Expand Down Expand Up @@ -1163,6 +1174,9 @@ bool errorbar(const std::vector<NumericX> &x, const std::vector<NumericY> &y, co
template<typename Numeric>
bool named_plot(const std::string& name, const std::vector<Numeric>& y, const std::string& format = "")
{
// Make sure python is initialized.
detail::_interpreter::get();

PyObject* kwargs = PyDict_New();
PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str()));

Expand All @@ -1187,6 +1201,9 @@ bool named_plot(const std::string& name, const std::vector<Numeric>& y, const st
template<typename Numeric>
bool named_plot(const std::string& name, const std::vector<Numeric>& x, const std::vector<Numeric>& y, const std::string& format = "")
{
// Make sure python is initialized.
detail::_interpreter::get();

PyObject* kwargs = PyDict_New();
PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str()));

Expand All @@ -1212,6 +1229,9 @@ bool named_plot(const std::string& name, const std::vector<Numeric>& x, const st
template<typename Numeric>
bool named_semilogx(const std::string& name, const std::vector<Numeric>& x, const std::vector<Numeric>& y, const std::string& format = "")
{
// Make sure python is initialized.
detail::_interpreter::get();

PyObject* kwargs = PyDict_New();
PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str()));

Expand All @@ -1237,6 +1257,9 @@ bool named_semilogx(const std::string& name, const std::vector<Numeric>& x, cons
template<typename Numeric>
bool named_semilogy(const std::string& name, const std::vector<Numeric>& x, const std::vector<Numeric>& y, const std::string& format = "")
{
// Make sure python is initialized.
detail::_interpreter::get();

PyObject* kwargs = PyDict_New();
PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str()));

Expand All @@ -1262,6 +1285,9 @@ bool named_semilogy(const std::string& name, const std::vector<Numeric>& x, cons
template<typename Numeric>
bool named_loglog(const std::string& name, const std::vector<Numeric>& x, const std::vector<Numeric>& y, const std::string& format = "")
{
// Make sure python is initialized.
detail::_interpreter::get();

PyObject* kwargs = PyDict_New();
PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str()));

Expand Down Expand Up @@ -1322,7 +1348,7 @@ void text(Numeric x, Numeric y, const std::string& s = "")
Py_DECREF(res);
}

void colorbar(PyObject* mappable = NULL, const std::map<std::string, float>& keywords = {})
inline void colorbar(PyObject* mappable = NULL, const std::map<std::string, float>& keywords = {})
{
if (mappable == NULL)
throw std::runtime_error("Must call colorbar with PyObject* returned from an image, contour, surface, etc.");
Expand Down Expand Up @@ -1611,6 +1637,9 @@ inline void tick_params(const std::map<std::string, std::string>& keywords, cons

inline void subplot(long nrows, long ncols, long plot_number)
{
// Make sure interpreter is initialized
detail::_interpreter::get();

// construct positional args
PyObject* args = PyTuple_New(3);
PyTuple_SetItem(args, 0, PyFloat_FromDouble(nrows));
Expand Down Expand Up @@ -1670,6 +1699,9 @@ inline void title(const std::string &titlestr, const std::map<std::string, std::

inline void suptitle(const std::string &suptitlestr, const std::map<std::string, std::string> &keywords = {})
{
// Make sure interpreter is initialized
detail::_interpreter::get();

PyObject* pysuptitlestr = PyString_FromString(suptitlestr.c_str());
PyObject* args = PyTuple_New(1);
PyTuple_SetItem(args, 0, pysuptitlestr);
Expand Down Expand Up @@ -1700,7 +1732,7 @@ inline void axis(const std::string &axisstr)
Py_DECREF(res);
}

void axvline(double x, double ymin = 0., double ymax = 1., const std::map<std::string, std::string>& keywords = std::map<std::string, std::string>())
inline void axvline(double x, double ymin = 0., double ymax = 1., const std::map<std::string, std::string>& keywords = std::map<std::string, std::string>())
{
// construct positional args
PyObject* args = PyTuple_New(3);
Expand Down