I am using the example at How to show data labels when you mouse over data to make an image where data appears when you mouse over points. This works really well but is ther
There may be ways to make things work with svg and a bit of javascript or pdf tooltips, as you suggested (I didn't know pdf tooltips existed until you mentioned them!).
As an aside, I should take a moment to mention mpld3 which recreates matplotlib figures as javascript visualizations using d3
. It does allow for very sharable, interactive figures and has some examples of making interactive tooltips.
However, I'm not sure how to make the matplotlib's svg files generally interactive, and you mentioned that you'd rather not go the javascript route, so I'll walk you through building a "stand-alone" executable (or, rather, a directory with an executable and associated libraries).
cx_freeze
and matplotlib
At least with regards to packaging an executable, I'd recommend cx_freeze
. There are plenty of other options (e.g. pyinstaller
, py2exe
, py2app
, etc), but most of them are platform-specific and a bit too "magical" for my tastes. cx_freeze
requires a bit more knowledge to use, but it's quite reliable, and not too hard to use once you're aware of what needs to be included.
First off, the complete example I'm about to walk you through is available here: https://gist.github.com/joferkington/9214844 It uses an example script and data you gave as part of an earlier question.
The key is to build a setup.py
file that correctly references 1) matplotlib's data files and 2) any data you need to include with your code for it to run correctly.
After that, it's as simple as python setup.py build_exe
and tar the build directory it creates to send to other people. (You may likely want to do something a bit fancier. It's possible to make shell scripts that contain the tarred data, libraries, and executable, but I'll skip that part here.)
setup.py
fileOn with the setup.py
. Let's assume you have a simple script called plot.py
that contains some basic plotting code and a file called data.csv
with the data that you want to plot with matplotlib
, etc. The setup.py
file for cx_freeze
would look something like this: (Also, for simplicity, I'm assuming you're using the Tk backend for matplotlib. Things will look slightly different if you're not.)
import cx_Freeze
import sys
import matplotlib
base = None
if sys.platform == "win32":
base = "Win32GUI"
executables = [
cx_Freeze.Executable("plot.py", base = base),
]
build_exe_options = {"includes":["matplotlib.backends.backend_tkagg"],
"include_files":[(matplotlib.get_data_path(), "mpl-data"),
('data.csv', 'data.csv')],
"excludes":[],
}
cx_Freeze.setup(
name = "script",
options = {"build_exe": build_exe_options},
version = "0.0",
description = "A basic example",
executables = executables)
Most of this is boilerplate. The key parts are:
x_Freeze.Executable("plot.py", base = base)
)"includes"
section in the build_exe_options
. cx_freeze
will try to auto-guess what modules it needs to include, but there are cases where it's not possible to detect everything it needs. This section allows you to specify additional modules to explicitly include. The matplotlib backends usually aren't auto-detected correctly, so you'll need to explicitly include whichever backend you're using."include_files"
section in the build_exe_options
. This indicates any additional data files that need to be included. Matplotlib has some data files (icons, etc) that need to be shipped alongside the code and libraries for things to function correctly. The line (matplotlib.get_data_path(), "mpl-data")
gets these files and puts them in a folder called "mpl-data" inside the build directory. Similarly, the line ('data.csv', 'data.csv')
gets your "data.csv" file and saves it with the same name in the build directory.I'll take a second to mention the "excludes"
option. This is entirely optional, but cx_freeze
will usually include many libraries that aren't actually required for your script to function. If you want to slim down the size of the file you're distributing, you may want to list particular python modules to exclude here. (e.g. "excludes":['PyQt4', 'scipy']
)
The rest is fairly self-explanatory. You may want to fill in the description, version, etc, but it's not required to build an executable.
So at this point, we have a directory with contents similar to the following:
$ ls
data.csv plot.py setup.py
data.csv
has our data, plot.py
is the script to plot it, and setup.py
is as described above.
To build an executable, we'd run
python setup.py build_exe
You'll get a long log of the build and exactly what it's copying over (probably along with some warnings that can be safely ignored in most cases). (This is useful information for debugging what's wrong with your setup.py
file.)
After it completes you'll notice a new directory called build
.
$ ls
build data.csv plot.py setup.py
At this point, build
will contain a single directory named something similar to:
$ ls build
exe.linux-x86_64-2.7
The exe.whatever
directory contains the libraries, data, and executable that you'll need to distribute to people for things to run correctly.
To see if it works, try (note the explict cd
into the directory!! More on that in a bit.):
$ cd build/exe.linux-x86_64-2.7
$ ./plot
(Obviously, if your file above wasn't called plot.py
, the executable won't be called plot
, but you get the idea.)
At this point, you could tar up the exe.whatever
directory (probably want to rename it before tarring), ship it out, and tell people to run it by untarring and calling cd name_of_dir; ./plot
.
I mentioned that we currently need to explictly cd
into the directory before running things. This is purely a result of the fact that plot.py
looks for a file called data.csv
in the current directory.
In other words, there's a line in plot.py
that does:
df = pd.read_csv('data.csv', ...)
We made setup.py
smart enough to include data.csv
but the code that reads it in expects it to be in the current directory.
You have two options:
cd
into the directory before running the script (In practice, ship a short script that cd
s in, runs the program, and cd
s back out). This is useful as a last resort if you don't want to bother with the second option.The second option is better for a number of reasons, but you'll have to modify your script (plot.py
, in this case) slightly.
Normally, you'd use the path to __file__
to determine the location relative to the script itself. However, with cx_freeze
, __file__
won't be defined, and the path you want is that of sys.executable
instead. For that reason, you usually do something like this: (From the cx_freeze faq: http://cx-freeze.readthedocs.org/en/latest/faq.html#data-files)
def find_data_file(filename):
if getattr(sys, 'frozen', False):
# The application is frozen
datadir = os.path.dirname(sys.executable)
else:
# The application is not frozen
# Change this bit to match where you store your data files:
datadir = os.path.dirname(__file__)
return os.path.join(datadir, filename)
In that case, you'd modify your code that does:
pd.read_csv('data.csv', ...)
to do:
pd.read_csv(find_data_file('data.csv'), ...)
instead. (This hasn't been done in the plot.py
file in the gist I linked to originally. I'll leave it to the reader as an exercise.)
Once we've done that, you can call /path/to/where/the/directory/gets/copied/plot
directly regardless of what the current working directory is.
I won't say too much on this topic. There are a lot of ways to handle this. With cx_freeze
, you're shipping a folder full of libraries and a single executable.
In the simplest case, you just tar it up, and tell people to untar and run where/they/extracted/it/name_of_the_execuctable
. You might want to rename the folder from exe.linux-x86_64-2.7
to something more like my_package
and include a shell script called run_this
or something, but that's up to you.
In other cases you may want to write a wrapper script or even a .desktop
file. Desktop files have to have absolute paths, so you'll need to do a bit more in that case. Usually, you write an installer script of some sort that modifies whatever.desktop
to point to the absolute path of where your program gets installed.
It's possible to embed the tarred data, libraries, and executable into a "self-extracting" install script. There are examples on the web if you want to dig around for them. You could also build an .rpm or .deb. Again, I'll skip the detailed example and leave that to you to figure out.
Overall, for what you seem to be doing, shipping a tarball and a README is probably the simplest route.