diff -Nru pyx-0.11.1/CHANGES pyx-0.12.1/CHANGES --- pyx-0.11.1/CHANGES 2011-05-20 13:01:14.000000000 +0000 +++ pyx-0.12.1/CHANGES 2012-10-26 09:02:40.000000000 +0000 @@ -1,97 +1,75 @@ -PyX - -TODO: - - style module: - - make dash length available - - text module: - - repair PDF generation with unincluded standard fonts (currently broken) - - SlantFont & Co support - - keep an eye on PEP 324 (popen 5): it could make the texrunner much easier - - discuss reltransform: https://sourceforge.net/mailarchive/forum.php?thread_id=9137692&forum_id=23700 - - add encoding; check whether encoding=ascii works well for the common existing cases - - write graphics driver for pgf.sty from LaTeX - - ignore "(Please type a command or say `\end')" in preamble method - - properly handle file names with spaces in (like "C:\program files\texmf\tex\latex\base\article.cls" in - texmessage.load - - graph.graph module - - common bboxes for different graphs - - 3d graphs, circular graphs - - axis painter labelattrs=None + automatic axis partitioning leads to no valid partitioning - - when all titles are None, a graph key currently raises an exception - - translatecanvas should be the default in graph.style.symbol - - automatic scaling of y-axis according to subset of data shown on x-axis - - graph.axis.timeaxis.timetick must not override datetime.datetime __init__ method (since datetime.datetime - instances are immutable like ints) - - graph.style and graph.data modules - - fix docu of gridattrs functionality of axis painters - (how can one, e.g. draw a grid at the ticks and not at the subticks) - - make graph.style.symbol.diamond a graph.style.symbol instance (to be discussed) - - graph.data module: - - use csv module (new in 2.3) - - Check for "inf" and "nan" in datafiles. - (When plotted, the graph does not complain, but the output will be broken; - this depends on the plattform and is not only an issue within the graph system) - - graph.style module: - - add styles using curves instead of lines - - graph.texter module: - - rename period argument of decimal texters to recurring_decimal or something similar (as pointed out - by Tim Head) - - canvas module: - - contructor should only take a texrunner keyword argument - - SVG support - - stroking of patterns does not work in PDF - - style and color module: - - support modification of existing styles via __call__ - - bitmap module: - - ReusableStreamDecode? - - proper ASCII85Decode/HexDecode switch - - Support for transparent bitmaps? - - bbox module: - - rewrite height, etc. using properties +0.12.1 (2012/10/26): + - graph styles: + - fix drawing and clipping of gradient style + +0.12 (2012/10/17): + - canvas module: + - insert method now returns canvas wrapping item if attrs are given + - insert method allows specification of position where item is inserted + by before and after arguments + - new layer method allows creation of separated drawing layers for + grouping of drawing operations + - writeXXXfile methods now use "page_" and "write_" prefixes for passing + the keyword arguments to the page constructor and write method + - pipeGS now returns a file handle instead of writing to a file or to stdout + - the new method writeGSfile restores the previous pipeGS functionality + - support rendering as png for use in IPython notebooks (as suggested by + Nikolas Tezak) + - document module: + - writeXXXfile methods now support writing to stdout when filename is set + to "-" + - type 1 font modules: + - allow font slanting for T1builtinfont instances + - improve stripped font compatibility + - remove UniqueID lookup from embedded fonts in PostScript output (fixes + missing glyph issue with dvips and certain fonts) + - allow Type1 font usage without TeX and without AFM font metric + - add support for pfm font matrices + - epsfile module: + - fix race condition while generating bitmap for PDF inclusion + - fix file opening for bbox reading when using filelocator (reported by + Michael J Gruber) + - filelocator module: + - fix text mode line ending issue for MS Windows + - text module: + - fix subprocess call on MS Windows (closefds not functional not also + not required at all) + - fix pyxgraphics functionality because at least some versions of graphics + and friends seem to expect the file extension .def for the driver files + (thanks to Michael J Gruber) + - deco module: + - decorator to put text along a path; based on a patch by Michael J Gruber - dvifile module: - - support single-character mode - - fix problem reported by Gert Ingold: http://sourceforge.net/mailarchive/forum.php?thread_name=20090323081817.GA34112%40physik.uni-augsburg.de&forum_name=pyx-user - - deformer module: - - move helper path routines to some more generic place for paths? - - pdffile module needed (similar to epsfile) - - open discussion: - - clearly distingish between readers and writers. dvifile, epsfile, etc. might - be renamed to dvireader, epsreader ... - - support gsave and grestore in some way to make context handling easier? - - ps/pdfwriters: - - support colorspace argument (like c.writeEPSfile("bla", colorspace="cmyk")) - - move pyx.def, lfs-files etc. into a new share directory - - write fakepattern.py which substitutes some of the pattern functionality using - re-usable canvases - - libkpathsea bindung: - - create a ctypes based binding to replace the extension module implementation in the long - - Documentation: - - path module: - - describe + vs << vs join - - more details in path constructor: allowed pathels, first pathel... - - set method - - pattern module: - - check and update - - graphics section: - Changeable attributes should be explained at a central place. - - box module: - - linealign and circlealign - - need to be redone anyway - - graph module: - styles: document what data name the styles accept (require). Try also to be more - userfriendly and not to describe immediately the internals of the implementation: - (see e.g. class.style.symbol) - - document interface to access tick positions - - Examples: - - add an epsfile example (suggested by Stathis Sideris) - - add a small example line on y="f($1)" in one of the plot examples - - add a grid example - - FAQ: - - describe creation and modification (inplace and ``modify by new'') of - graph data + - fix for MS Windows: open virtual font files in binary mode + - graph modules: + - graphs: + - uses new canvas layers to stack graph components (fixing bug #1518414, + reported by Dominic Ford) + - add a flipped option to graphxy to exchange x and y coordinates + - add a 1d graph "graphx" (use case: convert a value to a color) + - add hiddenaxes layer to the 3d graph + - add linkedaxes for the xy-plane in 3d like in 2d (works now properly due + to the hiddenaxes feature) + - graph styles: + - add density style + - add gradient style to convert a value to a color using a 1d graph + - add a usenames dictionary to the pos style (like rangepos had it already) + - graph data: + - add a join data provide which adds concatenates several data sources + - axis module: + - divisor was not properly taken into account in tick handling (axis + range extension and range rating) + - added the metapost module: + - create smooth paths from a series of points + - bitmap module: + - new fundamental constructor based on arbitrary affine transformations + - add ASCIIHexDecode end marker + - color module: + - add rgbgradient and cmykgradient to force color space + - mesh module: + - add ASCIIHexDecode end marker + - sourceforge.net: + - upgrade PyX project page to the Allura platform 0.11.1 (2011/05/20): diff -Nru pyx-0.11.1/PKG-INFO pyx-0.12.1/PKG-INFO --- pyx-0.11.1/PKG-INFO 2011-05-20 13:42:14.000000000 +0000 +++ pyx-0.12.1/PKG-INFO 2012-10-26 09:56:29.000000000 +0000 @@ -1,12 +1,12 @@ Metadata-Version: 1.0 Name: PyX -Version: 0.11.1 +Version: 0.12.1 Summary: Python package for the generation of PostScript and PDF files Home-page: http://pyx.sourceforge.net/ Author: Jörg Lehmann, André Wobst Author-email: pyx-devel@lists.sourceforge.net License: GPL -Download-URL: https://downloads.sourceforge.net/project/pyx/pyx/0.11.1/PyX-0.11.1.tar.gz +Download-URL: https://downloads.sourceforge.net/project/pyx/pyx/0.12.1/PyX-0.12.1.tar.gz Description: PyX is a Python package for the creation of PostScript and PDF files. It combines an abstraction of the PostScript drawing model with a TeX/LaTeX interface. Complex tasks like 2d and 3d plots in publication-ready quality are diff -Nru pyx-0.11.1/contrib/dviconvert.py pyx-0.12.1/contrib/dviconvert.py --- pyx-0.11.1/contrib/dviconvert.py 2011-05-19 11:11:25.000000000 +0000 +++ pyx-0.12.1/contrib/dviconvert.py 2012-10-16 20:56:07.000000000 +0000 @@ -18,6 +18,9 @@ # along with epstopng; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +import sys, os +sys.path.insert(0, "%s/.." % (os.path.dirname(__file__) or ".")) + from optparse import OptionParser from pyx import * from pyx import bbox, version @@ -47,10 +50,10 @@ if not dvipage: break if options.paperformat: - aligntrafo = trafo.translate(-unit.t_inch, unit.t_inch + paperformat.height) + aligntrafo = trafo.translate(unit.t_inch, -unit.t_inch + options.paperformat.height) aligneddvipage = canvas.canvas([aligntrafo]) aligneddvipage.insert(dvipage) - p = document.page(aligneddvipage, paperformat=paperformat) + p = document.page(aligneddvipage, paperformat=options.paperformat, centered=0) else: p = document.page(dvipage) d.append(p) diff -Nru pyx-0.11.1/debian/changelog pyx-0.12.1/debian/changelog --- pyx-0.11.1/debian/changelog 2012-05-17 08:02:37.000000000 +0000 +++ pyx-0.12.1/debian/changelog 2013-05-19 06:50:20.000000000 +0000 @@ -1,8 +1,18 @@ -pyx (0.11.1-2build1) quantal; urgency=low +pyx (0.12.1-2) unstable; urgency=low - * No changes rebuild for libkpathsea6 transition + * Upload to unstable. - -- Angel Abad Thu, 17 May 2012 10:02:17 +0200 + -- Stuart Prescott Sun, 19 May 2013 16:50:09 +1000 + +pyx (0.12.1-1) experimental; urgency=low + + * New upstream release + * Update maintainer address. + * Update copyright format URL. + * Bump standards version to 3.9.4 (no changes required). + * Drop postinst that was needed for lenny->squeeze upgrades. + + -- Stuart Prescott Mon, 17 Dec 2012 13:45:12 +0000 pyx (0.11.1-2) unstable; urgency=low diff -Nru pyx-0.11.1/debian/compat pyx-0.12.1/debian/compat --- pyx-0.11.1/debian/compat 2009-09-20 15:27:23.000000000 +0000 +++ pyx-0.12.1/debian/compat 2012-12-16 01:01:33.000000000 +0000 @@ -1 +1 @@ -7 +9 diff -Nru pyx-0.11.1/debian/control pyx-0.12.1/debian/control --- pyx-0.11.1/debian/control 2011-11-27 21:05:56.000000000 +0000 +++ pyx-0.12.1/debian/control 2012-12-16 18:18:25.000000000 +0000 @@ -1,14 +1,15 @@ Source: pyx -Maintainer: Stuart Prescott -Standards-Version: 3.9.2 +Maintainer: Stuart Prescott +Standards-Version: 3.9.4 Section: python Priority: optional -Build-Depends: debhelper (>= 8.9.5~), +Build-Depends: debhelper (>= 9~), python-all-dev (>= 2.6.6-3~), libkpathsea-dev, python, texlive-latex-base Build-Depends-Indep: python-sphinx (>= 1.0.7+dfsg), + libjs-mathjax, python-imaging, ghostscript, texlive-latex-extra, @@ -16,7 +17,6 @@ texlive-fonts-recommended, tipa Homepage: http://pyx.sourceforge.net/ -DM-Upload-Allowed: yes Package: python-pyx Architecture: any diff -Nru pyx-0.11.1/debian/copyright pyx-0.12.1/debian/copyright --- pyx-0.11.1/debian/copyright 2011-11-27 19:50:20.000000000 +0000 +++ pyx-0.12.1/debian/copyright 2012-12-16 18:15:53.000000000 +0000 @@ -1,4 +1,4 @@ -Format: http://dep.debian.net/deps/dep5/ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: PyX Upstream-Contact: Jörg Lehmann Michael Schindler @@ -6,9 +6,9 @@ Source: http://pyx.sourceforge.net/ Files: * -Copyright: © 2002-2011, Jörg Lehmann - © 2003-2011, Michael Schindler - © 2002-2011, André Wobst +Copyright: © 2002-2012, Jörg Lehmann + © 2003-2012, Michael Schindler + © 2002-2012, André Wobst License: GPL-2+ Files: pyx/data/afm/* @@ -34,7 +34,7 @@ Copyright: © 2003-2006, Graham Wilson © 2007, Thomas Viehmann © 2007, Ernesto Nadir Crespo Avila - © 2009-2011 Stuart Prescott + © 2009-2012 Stuart Prescott License: GPL-2+ License: GPL-2+ diff -Nru pyx-0.11.1/debian/patches/sphinx-mathjax.patch pyx-0.12.1/debian/patches/sphinx-mathjax.patch --- pyx-0.11.1/debian/patches/sphinx-mathjax.patch 2011-05-20 23:30:40.000000000 +0000 +++ pyx-0.12.1/debian/patches/sphinx-mathjax.patch 2012-12-17 13:29:41.000000000 +0000 @@ -7,13 +7,14 @@ # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. --extensions = ['mathjax'] -+extensions = ['sphinx.ext.pngmath'] +-extensions = ['sphinx.ext.mathjax', 'sphinx.ext.todo'] ++extensions = ['sphinx.ext.pngmath', 'sphinx.ext.todo'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] -@@ -219,4 +219,3 @@ +@@ -226,5 +226,4 @@ # -- Other options ------------------------------------------------------------ --mathjax_path = 'http://mathjax.connectmv.com/MathJax.js' +-mathjax_path = 'http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=default' + todo_include_todos = True diff -Nru pyx-0.11.1/debian/python-pyx-doc.doc-base.examples pyx-0.12.1/debian/python-pyx-doc.doc-base.examples --- pyx-0.11.1/debian/python-pyx-doc.doc-base.examples 2009-10-01 15:46:18.000000000 +0000 +++ pyx-0.12.1/debian/python-pyx-doc.doc-base.examples 2012-12-16 18:08:12.000000000 +0000 @@ -1,7 +1,7 @@ Document: pyx-examples Title: PyX examples -Author: Jörg Lehmann , - Michael Schindler , +Author: Jörg Lehmann , + Michael Schindler , André Wobst Abstract: Simple examples of the PyX plotting library, illustrating various aspects of this Python library for plotting scientific data, including: diff -Nru pyx-0.11.1/debian/python-pyx-doc.doc-base.faq pyx-0.12.1/debian/python-pyx-doc.doc-base.faq --- pyx-0.11.1/debian/python-pyx-doc.doc-base.faq 2009-09-28 22:42:26.000000000 +0000 +++ pyx-0.12.1/debian/python-pyx-doc.doc-base.faq 2012-12-17 13:42:22.000000000 +0000 @@ -5,5 +5,9 @@ using Python and LaTeX. Section: Science/Data Analysis +Format: HTML +Index: /usr/share/doc/python-pyx-doc/faq/index.html +Files: /usr/share/doc/python-pyx-doc/faq/*.html + Format: PDF Files: /usr/share/doc/python-pyx-doc/pyxfaq.pdf diff -Nru pyx-0.11.1/debian/python-pyx-doc.doc-base.gallery pyx-0.12.1/debian/python-pyx-doc.doc-base.gallery --- pyx-0.11.1/debian/python-pyx-doc.doc-base.gallery 2009-10-01 16:36:17.000000000 +0000 +++ pyx-0.12.1/debian/python-pyx-doc.doc-base.gallery 2012-12-16 18:10:18.000000000 +0000 @@ -1,7 +1,7 @@ Document: pyx-gallery Title: PyX gallery -Author: Jörg Lehmann , - Michael Schindler , +Author: Jörg Lehmann , + Michael Schindler , André Wobst Abstract: More complicated or real world examples of the PyX plotting library, illustrating various aspects of this Python library for plotting scientific diff -Nru pyx-0.11.1/debian/python-pyx-doc.doc-base.manual pyx-0.12.1/debian/python-pyx-doc.doc-base.manual --- pyx-0.11.1/debian/python-pyx-doc.doc-base.manual 2011-05-20 23:15:55.000000000 +0000 +++ pyx-0.12.1/debian/python-pyx-doc.doc-base.manual 2012-12-16 18:21:05.000000000 +0000 @@ -7,8 +7,8 @@ Section: Science/Data Analysis Format: HTML -Index: /usr/share/doc/python-pyx-doc/html/manual.html -Files: /usr/share/doc/python-pyx-doc/html/*.html +Index: /usr/share/doc/python-pyx-doc/manual/manual.html +Files: /usr/share/doc/python-pyx-doc/manual/*.html Format: PDF -Files: /usr/share/doc/python-pyx-doc/PyX.pdf +Files: /usr/share/doc/python-pyx-doc/manual.pdf diff -Nru pyx-0.11.1/debian/python-pyx-doc.docs pyx-0.12.1/debian/python-pyx-doc.docs --- pyx-0.11.1/debian/python-pyx-doc.docs 2011-05-21 11:00:24.000000000 +0000 +++ pyx-0.12.1/debian/python-pyx-doc.docs 2012-12-16 18:11:23.000000000 +0000 @@ -1,5 +1,6 @@ AUTHORS -faq/pyxfaq.pdf -manual/_build/latex/PyX.pdf -manual/_build/html/ +faq/_build/latex/pyxfaq.pdf +faq/_build/faq +manual/_build/latex/manual.pdf +manual/_build/manual gallery diff -Nru pyx-0.11.1/debian/python-pyx-doc.links pyx-0.12.1/debian/python-pyx-doc.links --- pyx-0.11.1/debian/python-pyx-doc.links 2011-05-20 23:45:09.000000000 +0000 +++ pyx-0.12.1/debian/python-pyx-doc.links 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -usr/share/javascript/jquery/jquery.min.js usr/share/doc/python-pyx-doc/html/_static/jquery.js diff -Nru pyx-0.11.1/debian/python-pyx.preinst pyx-0.12.1/debian/python-pyx.preinst --- pyx-0.11.1/debian/python-pyx.preinst 2009-11-03 22:42:05.000000000 +0000 +++ pyx-0.12.1/debian/python-pyx.preinst 1970-01-01 00:00:00.000000000 +0000 @@ -1,32 +0,0 @@ -#!/bin/sh - -set -e - -# TODO: remove this file after releasing Squeeze or python-central is fixed (see #479852) - -# python-pyx versions 0.9-4 to 0.10-0nmu1 used python-central -# python-pyx versions 0.10-0nmu2 and 0.10-0nmu3 use python-support but did -# not guarantee to make pycentral clean up after itself -# python-pyx versions 0.10-1 uses python-support and has this preinst to -# clean up pycentral files. -# -# For lenny->squeeze this script will be enough. For people upgrading -# the 0.10 packages that were in sid but without this script, if python-central -# is still installed (most likely!) then this script will clean things up and -# all is well. For a small number of people who installed the 0.10 packages -# without this package and (auto-)removed python-central, then there is some -# manual clean-up required. - -if [ "$1" = upgrade ] && dpkg --compare-versions "$2" lt "0.10-1" -then - if [ -x /usr/bin/pycentral ] - then - pycentral pkgremove python-pyx - fi - # if python-central is around then this should do nothing; if python-central - # has been removed (perhaps auto-removed) when upgrading from 0.10-0nmu{2,3} - # then force the clean-up anyway. - rm -rf /usr/lib/python2.?/site-packages/pyx/ -fi - -#DEBHELPER# diff -Nru pyx-0.11.1/debian/rules pyx-0.12.1/debian/rules --- pyx-0.11.1/debian/rules 2011-11-27 20:22:00.000000000 +0000 +++ pyx-0.12.1/debian/rules 2012-12-17 14:21:47.000000000 +0000 @@ -3,24 +3,20 @@ %: dh $@ --with python2 -binary: binary-arch binary-indep - -# build arch-indep files in the binary-indep target to prevent FTBFS on buildd -binary-indep: doc install - dh $@ --with sphinxdoc - doc: - make -C faq pdf + make -C faq all make -C manual all override_dh_auto_clean: make -C faq clean make -C manual clean - rm -rf manual/_build + rm -rf manual/_build faq/_build cd pyx/data/lfs; rm -f *.lfs *.aux *.log dh_auto_clean -override_dh_auto_build: +override_dh_auto_build-indep: doc + +override_dh_auto_build-arch: cd pyx/data/lfs; python createlfs.py dh_auto_build @@ -28,8 +24,11 @@ dh_auto_install rm $(CURDIR)/debian/tmp/usr/lib/python2.*/*-packages/pyx/data/lfs/createlfs.* -override_dh_installdocs: +override_dh_installdocs-indep: + mv manual/_build/html/ manual/_build/manual/ + mv faq/_build/html/ faq/_build/faq/ dh_installdocs -Xobjects.inv + dh_sphinxdoc override_dh_compress: dh_compress --all -X.pdf @@ -40,4 +39,4 @@ -type f -print0 | \ xargs -0r chmod 0644 -.PHONY: install doc binary binary-indep doc override_dh_auto_clean override_dh_auto_build override_dh_compress override_dh_installdocs +.PHONY: doc diff -Nru pyx-0.11.1/examples/3dgraphs/bar.py pyx-0.12.1/examples/3dgraphs/bar.py --- pyx-0.11.1/examples/3dgraphs/bar.py 2011-05-18 09:09:40.000000000 +0000 +++ pyx-0.12.1/examples/3dgraphs/bar.py 2012-10-17 08:14:51.000000000 +0000 @@ -4,6 +4,5 @@ g = graph.graphxyz(0, 0, size=5, x=graph.axis.bar(), y=graph.axis.bar(), z=None, z2=graph.axis.lin()) g.plot(graph.data.data(graph.data.points([[1, 1, 1.4], [1, 2, 1.8], [2, 1, -0.5], [2, 2, 0.9]]), xname=1, yname=2, z2=3), [graph.style.barpos(fromvalue=0, frompathattrs=None), graph.style.bar(barattrs=[style.linejoin.bevel])]) -g.doplot() g.writeEPSfile("bar") g.writePDFfile("bar") diff -Nru pyx-0.11.1/examples/3dgraphs/surface.py pyx-0.12.1/examples/3dgraphs/surface.py --- pyx-0.11.1/examples/3dgraphs/surface.py 2011-05-18 09:09:40.000000000 +0000 +++ pyx-0.12.1/examples/3dgraphs/surface.py 2012-10-17 08:15:34.000000000 +0000 @@ -3,6 +3,5 @@ g = graph.graphxyz(size=4, x2=None, y2=None) g.plot(graph.data.file("surface.dat", x=1, y=2, z=3), [graph.style.surface()]) -g.doplot() g.writeEPSfile("surface") g.writePDFfile("surface") diff -Nru pyx-0.11.1/examples/3dgraphs/surface.txt pyx-0.12.1/examples/3dgraphs/surface.txt --- pyx-0.11.1/examples/3dgraphs/surface.txt 2011-05-18 09:09:40.000000000 +0000 +++ pyx-0.12.1/examples/3dgraphs/surface.txt 2012-10-17 08:16:33.000000000 +0000 @@ -2,24 +2,18 @@ This first simple example provides data for the x, y and z coordinates. PyX creates a rectangular grid from the data from the first two coordinates without -relying on a special ordering of the data. ... - -! In this example we turn off the other two axes x2 and y2, which would be -plotted on the opposite side of the bottom horizontal plane. The data are -plotted prior to the axes by the dodata call. - -! A missing point will create a hole in the surface. +relying on a special ordering of the data. ... A missing point will create a +hole in the surface. ! The surface is plotted using a mesh. In case of a huge number of data points, you may want to replace the mesh by a bitmap rasterized on a different (i.e. lower) resolution. This can reduce the file size and may also resolve various printing problems (unfortunately those are quite common with mesh data). To -enable the mesh by bitmap replacement feature use the option `meshasbitmap` -(together with its `meshasbitmap_resolution` option):: +enable the mesh by bitmap replacement feature use the option `mesh_as_bitmap` +(together with its `mesh_as_bitmap_resolution` option):: - d = document.document([document.page(g)]) - d.writeEPSfile("color", mesh_as_bitmap=1) - d.writePDFfile("color", mesh_as_bitmap=1) + g.writeEPSfile(write_mesh_as_bitmap=True) + g.writePDFfile(write_mesh_as_bitmap=True) !! The underlying rectangular grid is created by the gridpos style. By changing its parameters you can also create the base rectangular grid from others than diff -Nru pyx-0.11.1/examples/README pyx-0.12.1/examples/README --- pyx-0.11.1/examples/README 2011-05-18 09:09:40.000000000 +0000 +++ pyx-0.12.1/examples/README 2012-10-16 20:56:15.000000000 +0000 @@ -9,8 +9,8 @@ ! Some paragraphs (like the one you're just reading) are marked with a ''dangerous bend sign''. Some people will recognize this classification from the TeXbook by D.E. Knuth and we use it at the PyX example pages for the same -purpose. The bend mark parts of the description, which require some experiences -with PyX. On the other hand those parts can savely be ignored by PyX beginners. +purpose. The bend marks parts of the description, which require some experiences +with PyX. On the other hand, those parts can savely be ignored by PyX beginners. !! There are even some paragraphs marked as ''doubly dangerous''. The explanations given in those paragraphs provide some deeper insights of what's really going diff -Nru pyx-0.11.1/examples/axis/grid.py pyx-0.12.1/examples/axis/grid.py --- pyx-0.11.1/examples/axis/grid.py 1970-01-01 00:00:00.000000000 +0000 +++ pyx-0.12.1/examples/axis/grid.py 2012-10-16 21:13:30.000000000 +0000 @@ -0,0 +1,16 @@ +from math import pi +from pyx import * +from pyx.graph import axis + +xgridpainter = axis.painter.regular(gridattrs=[]) +ygridpainter = axis.painter.regular(gridattrs=[attr.changelist([style.linestyle.dashed, None])]) + +g = graph.graphxy(width=10, + x=axis.lin(painter=xgridpainter, + divisor=pi, texter=axis.texter.rational(suffix=r"\pi"), min=-4*pi, max=4*pi), + y=axis.lin(painter=ygridpainter)) + +g.plot(graph.data.function("y(x)=sin(x)/x", points=200)) + +g.writeEPSfile() +g.writePDFfile() diff -Nru pyx-0.11.1/examples/axis/grid.txt pyx-0.12.1/examples/axis/grid.txt --- pyx-0.11.1/examples/axis/grid.txt 1970-01-01 00:00:00.000000000 +0000 +++ pyx-0.12.1/examples/axis/grid.txt 2012-10-16 21:13:30.000000000 +0000 @@ -0,0 +1,21 @@ +Drawing a graph with grid lines + +The drawing of grid lines is performed by the axis painters of the graph. ... +In order to turn on a grid, one has to specify in the argument `gridattrs` a +list of attributes defining the way the grid should look like. In order to +enable a grid at the position of the axis ticks and subticks, it is enough to +set `gridattrs` to an empty list. In the example, this is done for the grid +corresponding to the x-axis. If one wants to control the style of the grid +lines or even turn them off selectively for the main or subticks, one can pass +`attr.changelist` instances in the list. The first item of the argument of this +class is then used for the main ticks, the second for the first subticks, and +so on. In the example, it is shown how to change the linestyle of the grid at +the main tick positions and to turn off the grid for the subticks by setting +the correpsonding value in the `attr.changelist` instance to `None`. + +! The example also shows a neat trick how to create an axis with axis labels at +fractions of pi. This can be achieved by passing a `divisor` argument to the +axis itself, which means that for the axis handling, all values are divided by +the corresponding value. Then, we automatically put ticks at the position of +rational number by means of `axis.texter.rational` and add the missing factor +pi at the end of the TeX output. diff -Nru pyx-0.11.1/examples/axis/painter.py pyx-0.12.1/examples/axis/painter.py --- pyx-0.11.1/examples/axis/painter.py 2011-05-18 09:09:40.000000000 +0000 +++ pyx-0.12.1/examples/axis/painter.py 2012-10-17 11:02:01.000000000 +0000 @@ -4,6 +4,6 @@ basepathattrs=[style.linewidth.THick, deco.earrow.large]) c = graph.axis.pathaxis(path.curve(0, 0, 3, 0, 1, 4, 4, 4), - graph.axis.linear(min=0, max=9, title="axis title", painter=mypainter)) + graph.axis.linear(min=0, max=11, title="axis title", painter=mypainter)) c.writeEPSfile("painter") c.writePDFfile("painter") diff -Nru pyx-0.11.1/examples/axis/painter.txt pyx-0.12.1/examples/axis/painter.txt --- pyx-0.11.1/examples/axis/painter.txt 2011-05-18 09:09:40.000000000 +0000 +++ pyx-0.12.1/examples/axis/painter.txt 2012-10-17 11:00:30.000000000 +0000 @@ -17,3 +17,7 @@ stroked across the axis. As it is common to sub-components of the axis, you need to pass the adapted instances (stored in the variable `mypainter` here) to the axis in its constructor. + +! In this example there is no tick at the end due to the axis range. You can +suppress individual ticks by the `manualticks` axis parameter with ticklevel +and labellevel being zero. diff -Nru pyx-0.11.1/examples/drawing/README pyx-0.12.1/examples/drawing/README --- pyx-0.11.1/examples/drawing/README 2011-05-18 09:09:39.000000000 +0000 +++ pyx-0.12.1/examples/drawing/README 2012-10-16 20:56:13.000000000 +0000 @@ -1,5 +1,5 @@ Basic drawing -In this first section we demonstrate basic drawing features of -PyX. The goal is to present to you some of the basic concepts -of the PyX package like canvases, paths and styles. +In this first section we demonstrate basic drawing features of PyX. The goal is +to present some of the basic concepts of the PyX package like canvases, paths +and styles. diff -Nru pyx-0.11.1/examples/drawing/metapost.py pyx-0.12.1/examples/drawing/metapost.py --- pyx-0.11.1/examples/drawing/metapost.py 1970-01-01 00:00:00.000000000 +0000 +++ pyx-0.12.1/examples/drawing/metapost.py 2012-10-16 20:56:13.000000000 +0000 @@ -0,0 +1,21 @@ +from pyx import * +from pyx.metapost.path import beginknot, endknot, smoothknot, tensioncurve + +p1, p2, p3, p4, p5 = (0, 0), (2, 1.33), (1.3, 3), (0.33, 2.33), (1, 1.67) +openpath = metapost.path.path([ + beginknot(*p1), tensioncurve(), smoothknot(*p2), tensioncurve(), + smoothknot(*p3), tensioncurve(), smoothknot(*p4), tensioncurve(), + endknot(*p5)]) +closedpath = metapost.path.path([ + smoothknot(*p1), tensioncurve(), smoothknot(*p2), tensioncurve(), + smoothknot(*p3), tensioncurve(), smoothknot(*p4), tensioncurve(), + smoothknot(*p5), tensioncurve()]) +c = canvas.canvas() +for p in [p1, p2, p3, p4, p5]: + c.fill(path.circle(p[0], p[1], 0.05), [color.rgb.red]) + c.fill(path.circle(p[0], p[1], 0.05), [color.rgb.red, trafo.translate(2, 0)]) +c.stroke(openpath) +c.stroke(closedpath, [trafo.translate(2, 0)]) + +c.writeEPSfile("metapost") +c.writePDFfile("metapost") diff -Nru pyx-0.11.1/examples/drawing/metapost.txt pyx-0.12.1/examples/drawing/metapost.txt --- pyx-0.11.1/examples/drawing/metapost.txt 1970-01-01 00:00:00.000000000 +0000 +++ pyx-0.12.1/examples/drawing/metapost.txt 2012-10-16 20:56:13.000000000 +0000 @@ -0,0 +1,23 @@ +Creating paths with MetaPost-like parameters + +Some simple paths are created by prescribing five points on the path. The +curves between them are automatically chosen such that the result looks smooth. ... + +Instead of constructing paths from their path elements, where all control +points must be given explicitly, we here specify only five points which must +lie on the path. The remaining control points are then internally determined by +the constraint that the curve should look smooth. + +In the first curve, the geometrical constraints are the following. The three +interior points have a continuous tangent and a continuous curvature. The two +end points demand a curvature not too different from that at their neighbor +(second/fourth point). + +For the closed curve, all points are of the continuous type. + +! The algorithm for calculating the curves comes from MetaPost. In the syntax +of MetaPost, the above example corresponds to the commands + + draw((0,0)..(2,1.33)..(1.3,3)..(0.33,2.33)..(1,1.67)); + draw((0,0)..(2,1.33)..(1.3,3)..(0.33,2.33)..(1,1.67)..cycle); + diff -Nru pyx-0.11.1/examples/drawing2/parallel.txt pyx-0.12.1/examples/drawing2/parallel.txt --- pyx-0.11.1/examples/drawing2/parallel.txt 2011-05-18 09:09:40.000000000 +0000 +++ pyx-0.12.1/examples/drawing2/parallel.txt 2012-10-16 21:13:30.000000000 +0000 @@ -17,7 +17,7 @@ few path elements as possible. This is a striking feature of the parallel deformer, resulting in small EPS and PDF files, as well as in paths which can be processed further in PyX. In the example, the parallel curves for the curved -right part of the original path consists of a single Bzier curve only. +right part of the original path consists of a single Bézier curve only. ! Note that the order of the deformer attributes is not arbitrary, since the deforming operations do not commute. If you want first to smooth and then get diff -Nru pyx-0.11.1/examples/graphs/function.txt pyx-0.12.1/examples/graphs/function.txt --- pyx-0.11.1/examples/graphs/function.txt 2011-05-18 09:09:39.000000000 +0000 +++ pyx-0.12.1/examples/graphs/function.txt 2012-10-16 20:56:09.000000000 +0000 @@ -15,7 +15,7 @@ parameter `points` of the `graph.data.function` class to increase the number of sampling points from its default value of `100`. -Note that the default graph style for function data is `graph.style.line` synce +Note that the default graph style for function data is `graph.style.line` since PyX assumes a continuous x-range. ! You only need to pass the `min` and `max` parameters to the diff -Nru pyx-0.11.1/examples/graphs/join.dat pyx-0.12.1/examples/graphs/join.dat --- pyx-0.11.1/examples/graphs/join.dat 1970-01-01 00:00:00.000000000 +0000 +++ pyx-0.12.1/examples/graphs/join.dat 2012-10-16 21:13:30.000000000 +0000 @@ -0,0 +1,30 @@ +0 -0.0416523 0.00370405 -0.0120944 +0.0344828 0.0758855 0.144363 0.203587 +0.0689655 0.105887 0.259478 0.3624 +0.103448 0.170788 0.360286 0.542374 +0.137931 0.20317 0.407522 0.704534 +0.172414 0.311803 0.481457 0.80502 +0.206897 0.338232 0.594614 0.821449 +0.241379 0.302589 0.612485 0.906532 +0.275862 0.335894 0.59561 0.898951 +0.310345 0.286523 0.537869 0.842022 +0.344828 0.241982 0.471199 0.78992 +0.37931 0.201726 0.391884 0.645232 +0.413793 0.115665 0.282956 0.499897 +0.448276 0.126654 0.176257 0.328663 +0.482759 0.066533 0.0198417 0.110219 +0.517241 -0.0331831 -0.0761098 -0.141317 +0.551724 -0.0927296 -0.219183 -0.241559 +0.586207 -0.179708 -0.270902 -0.460771 +0.62069 -0.162048 -0.456294 -0.575559 +0.655172 -0.20805 -0.530463 -0.780346 +0.689655 -0.242298 -0.528865 -0.822735 +0.724138 -0.315267 -0.588264 -0.905236 +0.758621 -0.305853 -0.643168 -0.903536 +0.793103 -0.291477 -0.592055 -0.857436 +0.827586 -0.289402 -0.484567 -0.76292 +0.862069 -0.270358 -0.430461 -0.675236 +0.896552 -0.231194 -0.328504 -0.512663 +0.931034 -0.17519 -0.221731 -0.330941 +0.965517 -0.0567209 -0.0880629 -0.200636 +1 0.0449826 -0.0469508 0.0426738 diff -Nru pyx-0.11.1/examples/graphs/join.py pyx-0.12.1/examples/graphs/join.py --- pyx-0.11.1/examples/graphs/join.py 1970-01-01 00:00:00.000000000 +0000 +++ pyx-0.12.1/examples/graphs/join.py 2012-10-16 21:13:30.000000000 +0000 @@ -0,0 +1,21 @@ +from pyx import * + +g = graph.graphxy(width=8, key=graph.key.key()) + +As = [0.3, 0.6, 0.9] + +d = [graph.data.join([graph.data.function("y_a(x_a)=A*sin(2*pi*x_a)", context=dict(A=A)), + graph.data.file("join.dat", x_b=1, y_b=i+2)], + title=r"$A=%g$" % A) + for i, A in enumerate(As)] + +attrs = [color.gradient.RedBlue] + +g.plot(d, + [graph.style.pos(usenames=dict(x="x_a", y="y_a")), + graph.style.line(attrs), + graph.style.pos(usenames=dict(x="x_b", y="y_b")), + graph.style.symbol(graph.style.symbol.changesquare, symbolattrs=attrs, size=0.1)]) + +g.writeEPSfile() +g.writePDFfile() diff -Nru pyx-0.11.1/examples/graphs/join.txt pyx-0.12.1/examples/graphs/join.txt --- pyx-0.11.1/examples/graphs/join.txt 1970-01-01 00:00:00.000000000 +0000 +++ pyx-0.12.1/examples/graphs/join.txt 2012-10-16 21:13:30.000000000 +0000 @@ -0,0 +1,19 @@ +Joing multiple datasets for grouping in the graph key + +If you want to group datasets in the graph key, e.g., to compare simulation +results with measurement data ... or some approximate results with results from +a more detailed calculation, you can pass a list of datasets to the +`graph.data.join` class. + +When doing so, you have to assign unique names to the corresponding datasets. +In the example, we have used `x_a` and `y_a` as well as `x_b` and +`y_b` as names. Subsequently, one has to tell the graph style which data +columns—identified by the given name—it has to plot. This can be done +by inserting `graph.style.pos` instances before the respective graph style. By +passing a dictionary to the `usenames` argument, one specifies the +mapping from the data columns to the corresponding graph axes `x`, `y`, `x2`, +and so on. + +! When using the standard names `x`, `y`, etc. for the data columns, as is done +in most cases, PyX does insert the `graph.style.pos` instance mapping those +data columns to the axis with the same name, automatically. diff -Nru pyx-0.11.1/examples/graphstyles/cal.py pyx-0.12.1/examples/graphstyles/cal.py --- pyx-0.11.1/examples/graphstyles/cal.py 2011-05-18 09:09:40.000000000 +0000 +++ pyx-0.12.1/examples/graphstyles/cal.py 2012-10-26 08:21:26.000000000 +0000 @@ -3,7 +3,7 @@ class daystyle(graph.style._style): - def columnnames(self, privatedata, sharedata, graph, columnnames): + def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames): # register the new column names usecolumnnames = ["day", "month", "weekday", "note"] for columnname in usecolumnnames: @@ -11,7 +11,7 @@ raise ValueError("column '%s' missing" % columnname) return usecolumnnames - def adjustaxis(self, privatedata, sharedata, graph, columnname, data): + def adjustaxis(self, privatedata, sharedata, graph, plotitem, columnname, data): # adjust axes ranges if columnname == "month": graph.axes["x"].adjustaxis([(x, 0) for x in data]) @@ -36,15 +36,16 @@ graph.text_pt(x1_pt+3, y1_pt+3, point["note"], [text.size.tiny]) # create calendar data -year = 2011 +year = 2012 notes = {1: {17: r"\PyX{} 0.2 (2003)", 20: r"\PyX{} 0.5 (2004)", 22: r"\PyX{} 0.5.1 (2004)"}, 3: {30: r"\PyX{} 0.6 (2004)", 31: r"\PyX{} 0.3 ('03), \PyX{} 0.6.1 ('04)"}, 4: {4: r"\PyX{} 0.3.1 (2003)", 7: r"\PyX{} 0.6.2 (2004)", 27: r"\PyX{} 0.6.3 (2004)"}, - 5: {24: r"\PyX{} 0.9 (2006)", 5: r"\PyX{} 0.11 (2011)"}, + 5: {5: r"\PyX{} 0.11 (2011)", 20: r"\PyX{} 0.11.1 (2011)", 24: r"\PyX{} 0.9 (2006)"}, 7: {13: r"\PyX{} 0.8 (2005)"}, 8: {13: r"\PyX{} 0.8.1 (2005)", 22: r"\PyX{} 0.4 (2003)"}, 9: {17: r"\PyX{} 0.4.1 (2003)"}, - 10: {3: r"\PyX{} 0.10 (2007)", 7: r"\PyX{} 0.1 (2002)", 21: r"\PyX{} 0.7 (2004)"}, + 10: {3: r"\PyX{} 0.10 (2007)", 7: r"\PyX{} 0.1 (2002)", 12: r"\PyX{} 0.12 (2012)", + 21: r"\PyX{} 0.7 (2004)", 26: r"\PyX{} 0.12.1 (2012)"}, 12: {15: r"\PyX{} 0.7.1 (2004)"}} d = graph.data.points([(day, calendar.month_name[month], diff -Nru pyx-0.11.1/examples/graphstyles/changesymbol.py pyx-0.12.1/examples/graphstyles/changesymbol.py --- pyx-0.11.1/examples/graphstyles/changesymbol.py 2011-05-18 09:09:40.000000000 +0000 +++ pyx-0.12.1/examples/graphstyles/changesymbol.py 2012-10-16 08:19:42.000000000 +0000 @@ -15,15 +15,15 @@ self.gradient = gradient graph.style.symbol.__init__(self, symbol=symbol, symbolattrs=symbolattrs, **kwargs) - def columnnames(self, privatedata, sharedata, agraph, columnnames): + def columnnames(self, privatedata, sharedata, agraph, columnnames, dataaxisnames): # register the new column names if self.sizecolumnname not in columnnames: raise ValueError("column '%s' missing" % self.sizecolumnname) if self.colorcolumnname not in columnnames: raise ValueError("column '%s' missing" % self.colorcolumnname) return ([self.sizecolumnname, self.colorcolumnname] + - graph.style.symbol.columnnames(self, privatedata, - sharedata, agraph, columnnames)) + graph.style.symbol.columnnames(self, privatedata, sharedata, agraph, + columnnames, dataaxisnames)) def drawpoint(self, privatedata, sharedata, graph, point): # replace the original drawpoint method by a slightly revised one diff -Nru pyx-0.11.1/examples/graphstyles/density.py pyx-0.12.1/examples/graphstyles/density.py --- pyx-0.11.1/examples/graphstyles/density.py 1970-01-01 00:00:00.000000000 +0000 +++ pyx-0.12.1/examples/graphstyles/density.py 2012-10-18 07:49:21.000000000 +0000 @@ -0,0 +1,39 @@ +from pyx import * + +# Mandelbrot calculation contributed by Stephen Phillips + +# Mandelbrot parameters +re_min = -2 +re_max = 0.5 +im_min = -1.25 +im_max = 1.25 +gridx = 100 +gridy = 100 +max_iter = 10 + +# Set-up +re_step = (re_max - re_min) / gridx +im_step = (im_max - im_min) / gridy +d = [] + +# Compute fractal +for re_index in range(gridx): + re = re_min + re_step * (re_index + 0.5) + for im_index in range(gridy): + im = im_min + im_step * (im_index + 0.5) + c = complex(re, im) + n = 0 + z = complex(0, 0) + while n < max_iter and abs(z) < 2: + z = (z * z) + c + n += 1 + d.append([re, im, n]) + +# Plot graph +g = graph.graphxy(height=8, width=8, + x=graph.axis.linear(min=re_min, max=re_max, title=r"$\Re(c)$"), + y=graph.axis.linear(min=im_min, max=im_max, title=r'$\Im(c)$')) +g.plot(graph.data.points(d, x=1, y=2, color=3, title="iterations"), + [graph.style.density(gradient=color.rgbgradient.Rainbow)]) +g.writeEPSfile() +g.writePDFfile() diff -Nru pyx-0.11.1/examples/graphstyles/density.txt pyx-0.12.1/examples/graphstyles/density.txt --- pyx-0.11.1/examples/graphstyles/density.txt 1970-01-01 00:00:00.000000000 +0000 +++ pyx-0.12.1/examples/graphstyles/density.txt 2012-10-16 21:13:30.000000000 +0000 @@ -0,0 +1,16 @@ +Drawing a density plot + +2 dimensional plots where the value of each point is represented by a color can +be created by the density style. ... The data points have to be spaced +equidistantly in each dimension with the possible exception of missing data. + +For data which is not equidistantly spaced but still arranged in a grid, +`graph.style.surface` can be used, which also provides a smooth representation +by means of a color interpolation between the mesh moints. Finally, for +completely unstructured data, `graph.style.rect` can be used. + +!The plot is encoded in an efficient way using a bitmap. Unfortunately, this +means, that the HSB color space cannot be used (due to limitations of the +bitmap color spaces in PostScript and PDF). Some of the predefined gradients in +PyX, e.g. `color.gradient.Rainbow`, cannot be used here. As a workaround, PyX +provides those gradients in other color spaces as shown in the example. diff -Nru pyx-0.11.1/examples/graphstyles/usesymbol.txt pyx-0.12.1/examples/graphstyles/usesymbol.txt --- pyx-0.11.1/examples/graphstyles/usesymbol.txt 2011-05-18 09:09:40.000000000 +0000 +++ pyx-0.12.1/examples/graphstyles/usesymbol.txt 2012-10-16 20:56:15.000000000 +0000 @@ -2,8 +2,8 @@ This example demonstrates how linestyles, symboltypes, colors, and other decorations can be changed in existing graph styles. ... We make use of the -`changelist' feature as an argument when using a style. Generally, all items in -the attribute lists can be iterable `changelists'. If the end of such a list is +`changelist` feature as an argument when using a style. Generally, all items in +the attribute lists can be iterable `changelists`. If the end of such a list is reached, the changelist restarts from its beginning (see e.g. the colors which are used several times). diff -Nru pyx-0.11.1/examples/path/README pyx-0.12.1/examples/path/README --- pyx-0.11.1/examples/path/README 2011-05-18 09:09:39.000000000 +0000 +++ pyx-0.12.1/examples/path/README 2012-10-16 20:56:13.000000000 +0000 @@ -1,5 +1,5 @@ Path features In this section we demonstrate the capabilities of paths in PyX. The goal is to -give an overview of those operations that can are intrinsic to paths and do not +give an overview of those operations that are intrinsic to paths and do not affect their drawing on a canvas. diff -Nru pyx-0.11.1/examples/text/font.txt pyx-0.12.1/examples/text/font.txt --- pyx-0.11.1/examples/text/font.txt 2011-05-18 09:09:39.000000000 +0000 +++ pyx-0.12.1/examples/text/font.txt 2012-10-16 20:56:09.000000000 +0000 @@ -2,7 +2,7 @@ In LaTeX, there are nice packages allowing to switch fonts. Hence, for a simple example we change the mode of the default texrunner instance to LaTeX and use -the `preamble` method to load the `palatino` package. ... +the `preamble` method to load the `times` package. ... !! In general, it is also favourable to employ LaTeX when using your own Type1 fonts. Still you can also use different fonts in plain TeX if you are familiar @@ -21,9 +21,10 @@ are therefore not explicitly included in the output of PyX. This behaviour is <> from LaTeX, where these standard 35 fonts usually are not contained in the standard font-map file `psfonts.map`. -If you say +If you say in the PyX configuration file - text.set(fontmaps="download35.map") + psfontmaps = psfonts.map download35.map + pdffontmaps = pdftex.map download35.map then PyX finds the corresponding fonts in the map-file and includes the fonts in the output EPS and PDF files. diff -Nru pyx-0.11.1/examples/text/textalongpath.py pyx-0.12.1/examples/text/textalongpath.py --- pyx-0.11.1/examples/text/textalongpath.py 1970-01-01 00:00:00.000000000 +0000 +++ pyx-0.12.1/examples/text/textalongpath.py 2012-10-16 20:56:09.000000000 +0000 @@ -0,0 +1,12 @@ +from pyx import * + +c = canvas.canvas() + +p = path.path(path.moveto(-2, 0), path.curveto(-1, 0, -1, 1, 0, 1), path.curveto(1, 1, 1, 0, 2, 0)) + +c.stroke(p, [deco.curvedtext("\PyX{} is fun!"), + deco.curvedtext("left", textattrs=[text.halign.left, text.vshift.mathaxis], arclenfrombegin=0.5, exclude=0.1), + deco.curvedtext("right", textattrs=[text.halign.right, text.vshift.mathaxis], arclenfromend=0.5, exclude=0.1)]) + +c.writeEPSfile("textalongpath") +c.writePDFfile("textalongpath") diff -Nru pyx-0.11.1/examples/text/textalongpath.txt pyx-0.12.1/examples/text/textalongpath.txt --- pyx-0.11.1/examples/text/textalongpath.txt 1970-01-01 00:00:00.000000000 +0000 +++ pyx-0.12.1/examples/text/textalongpath.txt 2012-10-24 10:16:35.000000000 +0000 @@ -0,0 +1,12 @@ +Text along path + +In order to set text along a given path, you can use the `curvedtext()` +decorator. The example shows a few useful parameter settings for this +decorator. + +! To output just the curved text, but not the path, you can use the +draw method of the canvas instead of the stroke method. By that the +path is omitted in the output completely. In contrast, if you set the +linewidth to zero instead, the path will still be visible, as those +lines will be rendered as the thinnest available linewidth on the +output device according to the PostScript and PDF specification. diff -Nru pyx-0.11.1/faq/Makefile pyx-0.12.1/faq/Makefile --- pyx-0.11.1/faq/Makefile 2011-05-18 09:09:37.000000000 +0000 +++ pyx-0.12.1/faq/Makefile 2012-10-16 20:56:07.000000000 +0000 @@ -1,51 +1,33 @@ -PYTHON ?= python -PDFLATEX ?= pdflatex -LATEX ?= latex - -default: pyxfaq.pdf - -clean: partclean - -rm pyxfaq.pdf glifaq.pdf glifaq.sty - -partclean: - -rm *.aux *.out *.toc *.log *.glo *.idx - -rm pyxversion.tex pyxdate.tex - -all: pdf - -pdf: pyxfaq.pdf glifaq.pdf - -pyxfaq.pdf: pyxfaq.tex pyxversion.tex pyxdate.tex glifaq.sty - $(PDFLATEX) pyxfaq - $(PDFLATEX) pyxfaq - $(PDFLATEX) pyxfaq - -glifaq.sty: glifaq.dtx glifaq.ins - $(LATEX) glifaq.ins +# Makefile for Sphinx documentation +# -glifaq.pdf: glifaq.dtx - $(PDFLATEX) glifaq.dtx - $(PDFLATEX) glifaq.dtx - -pyxversion.tex: ../pyx/version.py - $(PYTHON) -c "import sys;sys.path[:0]=[\"..\"];import pyx.version;print pyx.version.version+'%'" > pyxversion.tex - -pyxdate.tex: ../pyx/version.py - $(PYTHON) -c "import sys;sys.path[:0]=[\"..\"];import pyx.version;print pyx.version.date+'%'" > pyxdate.tex - -publicserver = shell.sourceforge.net -publicpath = /home/groups/p/py/pyx/htdocs - -public: - @bash -c 'yn=""; while [ "x"$$yn != "xy" ]; do echo -n "upload pyxfaq.pdf? [y/n] "; read yn; if [ "x"$$yn == "xn" ]; then exit 1; fi; done' - scp pyxfaq.pdf $(publicserver):$(publicpath) - -ssh $(publicserver) "cd $(publicpath); chmod -fR g+w pyxfaq.pdf" - -tipapyks.pdf: tipa.py - $(PYTHON) tipa.py "pYks" "pyks" - -tipapyx.pdf: tipa.py - $(PYTHON) tipa.py "pYx" "pyx" +PYTHON ?= python -tipapych.pdf: tipa.py - $(PYTHON) tipa.py "pY\\c c" "pych" +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean all html latexpdf + +all: html latexpdf + @echo "Done." + +clean: + -rm -rf $(BUILDDIR)/* *.eps *.pdf *.png + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +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." diff -Nru pyx-0.11.1/faq/conf.py pyx-0.12.1/faq/conf.py --- pyx-0.11.1/faq/conf.py 1970-01-01 00:00:00.000000000 +0000 +++ pyx-0.12.1/faq/conf.py 2012-10-16 20:56:07.000000000 +0000 @@ -0,0 +1,226 @@ +# -*- coding: utf-8 -*- +# +# pyxfaq documentation build configuration file, created by +# sphinx-quickstart on Sun Jun 12 16:54:07 2011. +# +# 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, os +sys.path.insert(0, '..') +import pyx.version + +# 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 ----------------------------------------------------- + +# 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. +extensions = ['sphinx.ext.todo'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +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'pyxfaq' +copyright = u'2011, Gert-Ludwig Ingold' + +# 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(pyx.version.version.split('.')[:1]) +# The full version, including alpha/beta/rc tags. +release = pyx.version.version + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +today = pyx.version.date +# 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 = [] + + +# -- 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 = 'default' + +# 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 = {} + +# 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 = 'PyX FAQ' + +# 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'] + +# 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 + +# Output file base name for HTML help builder. +htmlhelp_basename = 'pyxfaqdoc' + +todo_include_todos = True # gli + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +latex_paper_size = 'a4' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'pyxfaq.tex', u'Some frequently and not so frequently asked questions about PyX', + u'Gert-Ludwig Ingold', '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 + +# Additional stuff for the LaTeX preamble. +latex_preamble = r''' + \hypersetup{pdftitle={%s}, + pdfauthor={Gert-Ludwig Ingold }, + pdfsubject={FAQ for PyX}, + pdfkeywords={PyX, graphics, tipps and tricks, FAQ}} +''' % latex_documents[0][2] + +# 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 = [ + ('index', 'pyxfaq', u'pyxfaq Documentation', + [u'Gert-Ludwig Ingold'], 1) +] + diff -Nru pyx-0.11.1/faq/general_aspects_plotting.rst pyx-0.12.1/faq/general_aspects_plotting.rst --- pyx-0.11.1/faq/general_aspects_plotting.rst 1970-01-01 00:00:00.000000000 +0000 +++ pyx-0.12.1/faq/general_aspects_plotting.rst 2012-10-16 20:56:07.000000000 +0000 @@ -0,0 +1,28 @@ +==================================== +General aspects of plotting with PyX +==================================== + +How do I generate multipage output? +=================================== + +With versions 0.8 and higher it is possible to produce multipage output, +i.e. a Postscript or PDF file containing more than one page. In order to +achieve this, one creates pages by drawing on a canvas as usual and +appends them in the desired order to a document from which Postscript or +PDF output is produced. The following example serves as an illustration:: + + from pyx import * + + d = document.document() + for i in range(3): + c = canvas.canvas() + c.text(0, 0, "page %i" % (i+1)) + d.append(document.page(c, paperformat=document.paperformat.A4, + margin=3*unit.t\_cm, + fittosize=1)) + d.writePSfile("multipage") + +Here, ``d`` is the document into which pages are inserted by means of the +``append`` method. When converting from a canvas to a document page, the page +properties like the paperformat are specified. In the last line, output is +produced from document ``d``. diff -Nru pyx-0.11.1/faq/general_aspects_pyx.rst pyx-0.12.1/faq/general_aspects_pyx.rst --- pyx-0.11.1/faq/general_aspects_pyx.rst 1970-01-01 00:00:00.000000000 +0000 +++ pyx-0.12.1/faq/general_aspects_pyx.rst 2012-10-16 20:56:07.000000000 +0000 @@ -0,0 +1,160 @@ +====================== +General aspects of PyX +====================== + +The name of the game +==================== + +Originally, the name PyX was constructed as a combination of **P**\ ostscript, +i.e. the first output format supported by PyX, P\ **y**\ thon, i.e. the language +in which PyX is written, and Te\ **X**, i.e. the program which PyX uses for +typesetting purposes. Actually, the title of this question is a tribute to TeX +because it is taken from the first chapter of the TeX book [#texbook]_ where +the origin of the name TeX and its pronunciation are explained. + +Despite the ties between TeX and PyX, their pronunciation is quite different. +According to the developers of PyX, it should be pronounced as pyks. Please do +not pronounce it as pyx or pyç. + +.. todo:: + + Replace y in IPA by the correct sign (U+028F). + +.. _where_do_I_get_PyX: + +Where do I get the latest version of PyX? +========================================= + +The current release of PyX (as well as older ones) is freely available from +`pyx.sourceforge.net `_ where also a +subversion repository with the latest patches can be found. Possibly older +versions of PyX are also available as package for various Linux distributions: +see, for instance, `http://packages.debian.org/testing/python/python-pyx.html +`_ for information +on the \PyX package in Debian GNU/Linux, +`http://packages.gentoo.org/ebuilds/?pyx-0.7.1 +`_ for a Gentoo Linux ebuild, +and `http://www.novell.com/products/linuxpackages/professional/python-pyx.html +`_ +for the PyX package in the SUSE LINUX professional distribution. + +How can I determine the version of PyX running on my machine? +============================================================= + +Start a python session (usually by typing ``python`` at the system prompt) and +then type the following two commands (``>>>`` is the python prompt) + +>>> import pyx +>>> pyx.__version__ + +Note that there are two underscores before and after ``version``. + +How can I access older versions of PyX? +======================================= + +As at present it is not guaranteed that PyX is backward compatible, it may be +desirable to access an older version of PyX instead of adapting older code to +the current version of PyX. In order to do that, one needs the corresponding +PyX package (see :ref:`where_do_I_get_PyX` if you need to download it), which +should be unpacked below a directory, e.g. ``/home/xyz/Python``, where you +want to keep the various PyX versions. This will result in a subdirectory with +a name like ``PyX-0.11.1`` which contains the contents of the corresponding +package. You can then ask Python to first look in the appropriate directory +before looking for the current version of PyX by inserting the following code +(appropriately modified according to your needs) at the beginning of your +program before importing the PyX module:: + + import sys + sys.path.insert(0, "/home/xyz/Python/PyX-0.11.1") + +Including appropriate lines even if the current version of PyX is used, might +turn out to be helpful when the current version has become an old version +(unless you have no difficulties determining the PyX version by looking at your +code). + +If your operating system supports path expansion, you might use as an +alternative:: + + import sys, os + sys.path.insert(0, os.path.expanduser("~/Python/PyX-0.11.1")) + +which will expand the tilde to your home directory. + +Does PyX run under my favorite operating system? +================================================ + +Yes, if you have installed Python :ref:`what_is_python`) and TeX +(:ref:`what_is_tex`). Both are available for a large variety of operating +systems so chances are pretty good that you will get PyX to work on your +system. + +Under which versions of Python will PyX run? +============================================ + +PyX is supposed to work with Python 2.1 and above. However, most of the +development takes place under the current production version of Python (2.4.1 +by the time of this writing) and thus PyX is better tested with this version. +On the other hand, the examples and tests are verified to run with Python 2.1 +and above using the latest bugfix releases. PyX will not work with earlier +Python versions due to missing language features. + +The version of your Python interpreter can be determined by calling it with the +option ``-V``. Alternatively, you can simply start the interpreter and take a +look at the startup message. Note that there may be different versions of +Python installed on your system at the same time. The default Python version +need not be the same for all users. + +Does PyX provide a GUI to view the produced image? +================================================== + +No, PyX itself does not provide a means to view the produced image. The result +of a PyX run is an EPS (= Encapsulated PostScript) file, a PS (= PostScript) +file or a PDF (= Portable Document Format) file, which can be viewed, printed +or imported into other applications. + +There are several means of viewing PS and EPS files. A common way would be to +use ``ghostview`` which provides a user interface to the PostScript interpreter +``ghostscript``. More information about this software, which is available for a +variety of platforms, can be found at `http://www.cs.wisc.edu/~ghost/ +`_. If you do not own a printer which is +capable of printing PostScript files directly, ``ghostscript`` may also be +useful to translate PS and EPS files produced by PyX into something your +printer will understand. + +PDF files can be viewed by means of the ``Adobe Reader ®`` available from +`http://www.adobe.com/products/acrobat/readstep2.html +`_. On systems running +X11, ``xpdf`` might be an alternative. It is available from +`http://www.foolabs.com/xpdf/ `_. + +I am a Gnuplot user and want to try PyX. Where can I get some help? +=================================================================== + +There exists a tutorial by Titus Winters which explains how to perform standard +Gnuplot tasks with \PyX. The tutorial can be found at +`http://www.cs.ucr.edu/~titus/pyxTutorial/ +`_. + +Where can I get help if my question is not answered in this FAQ? +================================================================ + +The PyX sources contain a reference manual which is also available online at +`http://pyx.sourceforge.net/manual/ `_. +Furthermore, there exists a set of examples demonstrating various features of +PyX, which is available in the sources or can be browsed at +`http://pyx.sourceforge.net/examples.html +`_. If the feature you are looking +for is among them, using the appropriate part of the example code or adapting +it for your purposes may help. + +There is also a user discussion list about PyX which you can subscribe to at +`http://lists.sourceforge.net/lists/listinfo/pyx-user +`_. The archive of the +discussion list is available at +`http://sourceforge.net/mailarchive/forum.php?forum_name=pyx-user +`_. + +Finally, it might be worth checking `http://pyx.sourceforge.net/pyxfaq.pdf +`_ for an updated version of this FAQ. + +.. [#texbook] D.Knuth, *The TeX book* (Addison-Wesley, 1984) diff -Nru pyx-0.11.1/faq/glifaq.dtx pyx-0.12.1/faq/glifaq.dtx --- pyx-0.11.1/faq/glifaq.dtx 2011-05-18 09:09:37.000000000 +0000 +++ pyx-0.12.1/faq/glifaq.dtx 1970-01-01 00:00:00.000000000 +0000 @@ -1,427 +0,0 @@ -% \iffalse -%\NeedsTeXFormat{LaTeX2e} -%<*driver> -\def\fileversion{0.3} -\def\filedate{2004/12/10} -\ProvidesFile{glifaq.drv} -% -%\ProvidesPackage{glifaq} - [2004/12/10 Style for PyX FAQ (gli) v0.3] -%<*driver> -\documentclass{ltxdoc} -\usepackage{url} -\EnableCrossrefs -\CodelineIndex -\RecordChanges -\newcommand\PyX{P\kern-.3em\lower.5ex\hbox{Y}\kern-.18em X} -\newcommand{\acro}[1]{\textsc{#1}} -\begin{document} - \DocInput{glifaq.dtx} -\end{document} -% -% \fi -% -% \CharacterTable -% {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z -% Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z -% Digits \0\1\2\3\4\5\6\7\8\9 -% Exclamation \! Double quote \" Hash (number) \# -% Dollar \$ Percent \% Ampersand \& -% Acute accent \' Left paren \( Right paren \) -% Asterisk \* Plus \+ Comma \, -% Minus \- Point \. Solidus \/ -% Colon \: Semicolon \; Less than \< -% Equals \= Greater than \> Question mark \? -% Commercial at \@ Left bracket \[ Backslash \\ -% Right bracket \] Circumflex \^ Underscore \_ -% Grave accent \` Left brace \{ Vertical bar \| -% Right brace \} Tilde \~} -% -% \CheckSum{239} -% -% -% \title{The \texttt{glifaq} package} -% -% \author{Gert-Ludwig Ingold\\\small\url{}} -% \date{(v.\fileversion\ -- \filedate)} -% \maketitle -% -% \section{Introduction} -% The \texttt{glifaq} package has been developed to typeset the \acro{faq} of -% the \PyX{} graphics package for \acro{Python}. It might contain a few -% commands which are useful for other \acro{faq}s as well. -% -% \section{Usage} -% \label{sec:usage} -% The package \texttt{glifaq} is intended for use with the \texttt{scrartcl} -% class which is part of \acro{KOMA-Script}. \texttt{scrartcl} should therefore -% be chosen as document class. -% -% The options of the package \texttt{glifaq} mostly control the kind of -% questions which will be typeset. There are four kinds of questions: -% \begin{center} -% \begin{tabular}{ll} -% |a| & answered question\\ -% |c| & question where the answer has been checked by an expert\\ -% |o| & outdated question\\ -% |t| & question not fully answered yet (= to do) -% \end{tabular} -% \end{center} -% The following options are recognized by the package: -% \begin{description} -% \item[all] prints all questions -% \item[answered] prints only questions which have no ``to do'' status, i.e. -% they have a satisfying answer and might even have been checked -% \item[checked] prints only questions where the answers have been checked -% \item[outdated] prints only questions and answers which are outdated -% \item[todo] prints all questions where no satisfying answer has been -% formulated yet -% \item[unchecked] prints all questions which have not been checked yet -% \item[comments] if comments exist, they will be printed as well and a -% special mark will be put in the margin -% \item[notoc] the table of contents will not be printed -% \end{description} -% By default, all questions and the table of contents but no comments will be -% printed. If questions with ``to do'' status are printed, a special mark will -% be put in the margin. -% -% \DescribeMacro\question -% The most important task of the package \texttt{glifaq} is to define a command -% \begin{quote} -% |\question{|\meta{status}|}{|\meta{title}|}{|\meta{comment}|}{|^^A -% \meta{answer}|}| -% \end{quote} -% to handle the typesetting of questions and their answers. As indicated, this -% command takes four arguments: -% \begin{center} -% \begin{tabular}{ll} -% 1: & class of the question according to the table given above\\ -% & allowed values: |a|, |c|, |o|, |t|\\ -% 2: & title of the question\\ -% 3: & comments related to this question\\ -% 4: & the answer to the question -% \end{tabular} -% \end{center} -% -% \DescribeEnv{progcode} -% For the typesetting of code snippets, an environment |progcode| has been -% defined which will use a fixed space font. Unfortunately, verbatim code -% cannot be used in arguments as is the case here in macro |\question|. -% To guarantee proper spaces and in particular indenting, a tilde has to -% used instead of a space. -% -% In addition, there exist a few simple definitions which may be useful: -% -% \DescribeMacro\uaref -% The macro |\uaref| acts like the usual |\ref| command but puts an $\uparrow$ -% (|\uparrow|) in front of the reference. -% -% \DescribeMacro\toc -% The macro |\toc| replaces the usual |\tableofcontents| to allow for control -% via the |notoc| option. -% -% \DescribeMacro\PyX -% The macro |\PyX| defines the \PyX{} logo as employed by the developers of -% \PyX{} in their manual. -% -% \DescribeMacro\tipagraph -% In order to explain the pronunciation of \PyX, the |tipa| package is -% needed which cannot be expected to be present in every installation. -% Therefore, we provide via the |\tipagraph| command a way to alternatively -% include a graphics file. The command is used as follows -% \begin{quote} -% |\tipagraph{|\meta{tipa code}|}{|\meta{graphics filename}|}| -% \end{quote} -% -% \DescribeMacro\cs -% In some places, a backslash cannot be used verbatim, in particular in an -% argument of the |\question| macro. Using -% \begin{quote} -% \verb*+\cs +\meta{command name} -% \end{quote} -% will result in a backslash followed directly by the command name. -% -% \DescribeMacro\us -% The macro |\us| can be used to produce an underscore if there is no more -% direct way to do so. -% -% \DescribeMacro\hat -% Sometimes it may also be difficult to produce a hat character. In such cases, -% |\hat| can be useful. -% -% \DescribeMacro\cb -% The last macro of this type is |\cb| which allows to enclose an argument in -% curly braces. -% -% \DescribeMacro\ctan -% For references to files on \acro{ctan}, the macro |\ctan| can be used where -% the single argument should be the location of the file relative to the -% \acro{ctan} root. -% \DescribeMacro\ftpCTAN -% One of the \acro{ctan} ftp servers is coded into the package via the -% |\ftpCTAN| macro for direct reference from the \acro{pdf} version of the -% \acro{faq}. -% -% \DescribeMacro\new\DescribeMacro\changed -% Finally, the two macros |\new| and |\changed| allow to mark questions as -% new or changed with respect to an earlier version of the \acro{faq}. These -% macros should be used in the second argument of |\question| just behind the -% text defining the title of a question. -% -% \section{The Description of the Package Code} -% \StopEventually -% -% We first define a few new switches: -% \begin{center} -% \begin{tabular}{ll} -% |\if@a|& print answered questions if true\\ -% |\if@c|& print corrected questions if true\\ -% |\if@o|& print outdated questions if true\\ -% |\if@t|& print unanswered or not fully answered questions if true\\ -% |\ifc@mments|& print comments if true\\ -% |\ift@c|& insert table of contents if true -% \end{tabular} -% \end{center} -% By default, we print all questions and the table of contents but no comments. -% \begin{macrocode} -\newif\if@a \@atrue -\newif\if@c \@ctrue -\newif\if@o \@otrue -\newif\if@t \@ttrue -\newif\ifc@mments \c@mmentsfalse -\newif\ift@c \t@ctrue -% \end{macrocode} -% Now we define the various options and set the switches according to the -% options' definition given in section~\ref{sec:usage}. -% \begin{macrocode} -\DeclareOption{all}{\@atrue \@ctrue \@otrue \@ttrue} -\DeclareOption{answered}{\@atrue \@ctrue \@ofalse \@tfalse} -\DeclareOption{checked}{\@afalse \@ctrue \@ofalse \@tfalse} -\DeclareOption{outdated}{\@afalse \@cfalse \@otrue \@tfalse} -\DeclareOption{todo}{\@afalse \@cfalse \@ofalse \@ttrue} -\DeclareOption{unchecked}{\@atrue \@cfalse \@ofalse \@ttrue} -\DeclareOption{comments}{\c@mmentstrue} -\DeclareOption{notoc}{\t@cfalse} -\ProcessOptions\relax -% \end{macrocode} -% Next, we load some needed packages if they have not been loaded before. -% \begin{macrocode} -\RequirePackage{ifthen} -\RequirePackage{remreset} -\RequirePackage{pifont} -% \end{macrocode} -% The following macro will put a large cross in the margin to mark comments or -% questions with ``to do'' status. -% \begin{macrocode} -\newcommand{\put@ding}{\mbox{}\marginpar{\Huge\ding{56}}} -% \end{macrocode} -% -% Now comes the most important part of this package, the macro |\question|. -% This macro has to decide on the basis of the first argument and the -% options used with the package whether the current question is to be printed -% or not. -% -% It is assumed that in the section hierarchy, the question can take the role -% of a subsection or a subsubsection. Therefore, a mechanism has to be -% provided which tells the question its place in the hierarchy. Independent -% of whether the question acts like a subsection or subsubsection, the layout -% of the question title should be the one of a subsubsection. -% -% We introduce three switches: |\@printtrue| indicates that the question has -% to be printed while |\@margtrue| requests a mark to be put into the margin -% to indicate a question with ``to do'' status and |\@outdatedtrue| will mark a -% question as outdated. -% \begin{macrocode} -\newif\if@print -\newif\if@marg -\newif\if@outdated -% \end{macrocode} -% Below we will need to find out which level a question corresponds to. If the -% value of the counter \texttt{question} is not equal to 0, the level is -% already known. A value of 1 indicates that the questions are on the level -% of a \texttt{subsection} while a value of 2 implies that the questions are -% on the \texttt{subsubsection} level. Whenever a new section is started, we -% will need to find out the level of the questions in this section. Therefore, -% we reset the counter \texttt{question} whenever a new section is started. -% \begin{macrocode} -\newcounter{question}[section] -\setcounter{question}{0} -% \end{macrocode} -% Since we want to set the title of a question always in the fontsize of a -% \texttt{subsubsection}, we might need to temporarily change the fontsize -% of the \texttt{subsection} title. In order to be able to restore the original -% value, we save the value of |\size@subsection| which is used in the -% \acro{KOMA-Script} classes to define the fontsize of the \texttt{subsection} -% title. -% \begin{macrocode} -\let\save@sizesubsection\size@subsection -% \end{macrocode} -% -% Now we are ready to define the |\question| macro. First, defaults are set -% not to print the question and not to print a mark in the margin. If the status -% of the question corresponds to one asked for by the option passed to the -% package, we change the print switch to |\@printtrue|. A ``to do'' question -% will in addition ask to print a mark in the margin by setting |\@margtrue|. -% \begin{macrocode} -\newcommand{\question}[4]% -{\@printfalse\@margfalse\@outdatedfalse% - \ifthenelse{\equal{a}{#1}\and\boolean{@a}}{\@printtrue}{}% - \ifthenelse{\equal{c}{#1}\and\boolean{@c}}{\@printtrue}{}% - \ifthenelse{\equal{o}{#1}\and\boolean{@o}}{\@printtrue\@outdatedtrue}{}% - \ifthenelse{\equal{t}{#1}\and\boolean{@t}}{\@printtrue\@margtrue}{}% -% \end{macrocode} -% If the \texttt{question} counter has the value 0, we need to determine the -% level of the question. The question should at least be on -% \texttt{subsubsection} level if not higher. So we first assume it to be -% of this level and make corrections later on if necessary. The counter -% \texttt{question} is set to a level different from 0 (in this case to a -% value of 2 although this is nowhere exploited in the code). This counter -% will now be reset not only by a |\section| but also by a |\subsection|. -% Finally, we set the command |\questi@n| which will typeset the question title -% as \texttt{subsubsection}. -% -% In a second step, we check whether the question is on |\subsection| level -% instead of |\subsubsection| level as was assumed before. In this case, -% the counter \texttt{question} is set to 1 (again it is only important that -% the value is different from 0). This counter should no longer be reset -% by a |\subsection| so we remove the corresponding entry from the reset list. -% Finally, the command |\questi@n| typesetting the title of the question is -% set to |\subsection|. -% \begin{macrocode} - \ifthenelse{\value{question} = 0}{% - \setcounter{question}{2}% - \@addtoreset{question}{subsection}% - \let\questi@n\subsubsection% - \ifthenelse{\value{subsection} = 0}% - {\setcounter{question}{1}% - \@removefromreset{question}{subsection}% - \let\questi@n\subsection}% - {}% - }{} -% \end{macrocode} -% Now we are ready to typeset the question if this is what the options passed -% to the package ask for. Before typesetting the question title, we temporarily -% set the fontsize of the |\subsection| title to the fontsize of the -% |\subsubsection| just in case we are on the |\subsection| level. |\questi@n| -% does the typesetting of the title and a mark is put into the margin if -% requested by |\@margtrue|. -% -% After the title, a comment, if present and asked for, is typeset in sans -% serif and small fontsize. Finally, the answer, i.e. the contents of argument -% 4, is typeset. -% \begin{macrocode} - \if@print% - \let\size@subsection\size@subsubsection - \questi@n{#2 \if@outdated\outd@ted\fi}\if@marg\put@ding\else\fi% - \let\size@subsection\save@sizesubsection - \ifc@mments - \ifthenelse{\equal{}{#3}}{} - {\put@ding{\sffamily\small#3}\par}% - \else\fi - #4 - \else\fi} -% \end{macrocode} -% -% Since it is not possible to use verbatim code in macro arguments, we cannot -% typeset code snippets in an answer by using a \texttt{verbatim} environment. -% We therefore define the environment \texttt{progcode}. In order to ensure -% proper indentation, we make the tilde an active character and define -% it as space preceded by a |\strut| so that the space is not ignored. -% In addition, several layout parameters are set like a global indentation -% of the code and the use of a small fixed space font. -% \begin{macrocode} -\newenvironment{progcode} - {\list{}{\leftmargin\parindent\rightmargin\z@}% - \ttfamily\small% - \catcode`\~=13% - \def~{\strut\ }% - \item\relax} - {\endlist} -% \end{macrocode} -% To facilitate references preceded by a $\uparrow$ we define |\uaref| which -% works like the standard |\ref|. -% \begin{macrocode} -\DeclareRobustCommand\uaref[1]{$\uparrow$\ref{#1}} -% \end{macrocode} -% The option \texttt{notoc} only works if instead of |\tableofcontents| the -% command |\toc| defined here is used. -% \begin{macrocode} -\DeclareRobustCommand\toc[0]{\ift@c\tableofcontents\else\fi} -% \end{macrocode} -% The definition of the \PyX{} logo is copied from the code used by the \PyX{} -% developers in their manual. Here, we also provide the simple string ``PyX'' -% for use in the \acro{pdf} bookmarks. -% \begin{macrocode} -\DeclareRobustCommand\PyX[0]{\texorpdfstring% - {P\kern-.3em\lower.5ex\hbox{Y}\kern-.18em X}{PyX}% - } -% \end{macrocode} -% We now check for the presence of the |tipa| package. If present, it -% will be loaded and the command |\tipagraph| will hand over its first -% argument to the command |\textipa|. Otherwise, the |graphicx| package -% is loaded and |\tipagraph| will lead to the inclusion of the graphics file -% given in the second argument. The vertical shift of the box has been chosen -% by trial and error. The horizontal spacing is slightly different in the two -% alternatives but this should not give to rise to major problems. -% \begin{macrocode} -\IfFileExists{tipa.sty}% - {\usepackage{tipa}% - \newcommand{\tipagraph}[2]{\textipa{##1}}}% - {\usepackage{graphicx}% - \newcommand{\tipagraph}[2]{\raisebox{-2.727263bp}{\includegraphics{##2}}}} -% \end{macrocode} -% When a backslash character is needed as part of a verbatim command name, -% but verbatim code cannot be used, the macro |\cs| can be employed. -% Again, we take care of the requirements of the \acro{pdf} bookmarks. -% \begin{macrocode} -\DeclareRobustCommand\cs[1]{% - \texorpdfstring{\texttt{\char`\\}}{\textbackslash}#1% - } -% \end{macrocode} -% In order to avoid problems with verbatim code, we define |\us| and |\hat| -% to produce an underscore and a hat, respectively. -% \begin{macrocode} -\DeclareRobustCommand\us[0]{\texttt{\char`\_}} -\DeclareRobustCommand\hat[0]{\texttt{\char`\^}} -% \end{macrocode} -% The macro |\cb| encloses its argument in curly braces and should be used -% when verbatim code does not work. -% \begin{macrocode} -\DeclareRobustCommand\cb[1]{\texttt{\char`\{#1\char`\}}} -% \end{macrocode} -% For files on \acro{ctan}, we define the macro |\ctan| which is used as follows -% \begin{quote} -% |\ctan{|\meta{file location relative to CTAN root}|}| -% \end{quote} -% In order for the link to connect to a \acro{ctan} server, |\ftpCTAN| contains -% the URL of one of the \acro{ctan} servers which is chosen quite arbitrarily -% and could be replaced by another \acro{ctan} server. Note that we use -% |\path| instead of |\url| to avoid that a link is created to the second -% argument instead of the first one. -% \begin{macrocode} -\def\ftpCTAN{ftp://ctan.tug.org/tex-archive/} -\DeclareRobustCommand\ctan[1]{% - \href{\ftpCTAN#1}{\path{CTAN:#1}}% -} -% \end{macrocode} -% In order to mark questions as new or changed with respect to a previous -% release of the \acro{faq}, we define the macros |\new| and |\changed| which -% are intended to be used at the end of the second argument of |\question|. -% No output is generated for \acro{pdf} bookmarks. -% \begin{macrocode} -\DeclareRobustCommand\new[0]{\texorpdfstring% - {\quad\raisebox{0.3ex}{\fbox{\tiny\normalfont NEW}}}{} - } -\DeclareRobustCommand\changed[0]{\texorpdfstring% - {\quad\raisebox{0.3ex}{\fbox{\tiny\normalfont CHANGED}}}{} - } -% \end{macrocode} -% Outdated questions should be marked also in the \acro{pdf} bookmarks. -% \begin{macrocode} -\DeclareRobustCommand\outd@ted[0]{\texorpdfstring% - {\quad\colorbox{black}{\color{white}\small OUTDATED}} - {~(outdated)} - } -% \end{macrocode} -% \Finale -\endinput diff -Nru pyx-0.11.1/faq/glifaq.ins pyx-0.12.1/faq/glifaq.ins --- pyx-0.11.1/faq/glifaq.ins 2011-05-18 09:09:37.000000000 +0000 +++ pyx-0.12.1/faq/glifaq.ins 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ -\def\batchfile{glifaq.ins} -\input docstrip.tex -\generate{\file{glifaq.sty}{\from{glifaq.dtx}{package}}} -\endbatchfile diff -Nru pyx-0.11.1/faq/index.rst pyx-0.12.1/faq/index.rst --- pyx-0.11.1/faq/index.rst 1970-01-01 00:00:00.000000000 +0000 +++ pyx-0.12.1/faq/index.rst 2012-10-16 20:56:07.000000000 +0000 @@ -0,0 +1,24 @@ +.. pyxfaq documentation master file, created by + sphinx-quickstart on Sun Jun 12 16:54:07 2011. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Some frequently and not so frequently asked questions about PyX +=============================================================== + +.. topic:: Acknowledgements + + The following persons have in one way or the other, e.g. by asking good questions or providing answers, + contributed to this FAQ: Walter Brisken, Alejandro Gaita-Arinyo, Pierre Joyot, Jörg Lehmann, John Owens, + Michael Schindler, Gerhard Schmid, André Wobst. + + +.. toctree:: + :maxdepth: -1 + + general_aspects_pyx + python + general_aspects_plotting + plotting_graphs + other_plotting + tex_latex diff -Nru pyx-0.11.1/faq/other_plotting.rst pyx-0.12.1/faq/other_plotting.rst --- pyx-0.11.1/faq/other_plotting.rst 1970-01-01 00:00:00.000000000 +0000 +++ pyx-0.12.1/faq/other_plotting.rst 2012-10-16 20:56:07.000000000 +0000 @@ -0,0 +1,32 @@ +==================== +Other plotting tasks +==================== + +How can I rotate text? +====================== + +Text can be written at an arbitrary angle by specifying the appropriate +transformation as an attribute. The command :: + + c.text(0, 0, "Text", [trafo.rotate(60)]) + +will write at an angle of 60 degrees relative to the horizontal axis. If no +pivot is specified (like in this example), the text is rotated around the +reference point given in the first two arguments of ``text``. In the +following example, the pivot coincides with the center of the text:: + + c.text(0, 0, "Text", [text.halign.center,text.valign.middle,trafo.rotate(60)]) + +How can I clip a canvas? +======================== + +In order to use only a part of a larger canvas, one may want to clip it. This +can be done by creating a clipping object which is used when creating a canvas +instance:: + + clippath = path.circle(0.,0.,1.) + clipobject = canvas.clip(clippath) + c = canvas.canvas([clipobject]) + +In this example, the clipping path used to define the clipping object is a +circle. diff -Nru pyx-0.11.1/faq/plotting_graphs.rst pyx-0.12.1/faq/plotting_graphs.rst --- pyx-0.11.1/faq/plotting_graphs.rst 1970-01-01 00:00:00.000000000 +0000 +++ pyx-0.12.1/faq/plotting_graphs.rst 2012-10-16 20:56:07.000000000 +0000 @@ -0,0 +1,393 @@ +================== +Plotting of graphs +================== + +General aspects +=============== + +.. _mingraphdata: + +How do I generate a graph from data as simply as possible? +---------------------------------------------------------- + +Suppose that you have a data file ``x.dat`` containing values for ``x`` and +``y`` in two columns. Then the following code will do the job:: + + from pyx import * + + g = graph.graphxy(width=10) + g.plot(graph.data.file("x.dat", x=1, y=2)) + g.writeEPSfile("x") + +``graphxy`` creates a canvas (called ``g`` in this example) onto which the +graph will be drawn and it sets the default behavior including the axis. There +is, however, no default value for the width of the graph. In ``plot`` you have +to specify the name of the data file and the columns from which the data should +be taken. Finally, ``writeEPSfile`` will generate the postscript file ``x.eps`` +which you can view or print. + +A minimal example is also provided in the PyX distribution as +``examples/graphs/minimal.py``. + +.. _mingraphfunc: + +How do I generate a graph of a function as simply as possible? +-------------------------------------------------------------- + +The following example will draw a parabola:: + + from pyx import * + + g = graph.graphxy(width=10, + x=graph.axis.linear(min=-2, max=2) + ) + + g.plot(graph.data.function("y(x)=x**2")) + + g.writeEPSfile("x") + +Most of the code has been explained in :ref:`mingraphdata`. The main +difference is that here you need to specify minimum and maximum for the +``x``-axis so that PyX knows in which range to evaluate the function. + +Another, slightly more complex, example is also provided in the PyX +distribution as ``examples/graphs/piaxis.py``. + +How can I stack graphs? +----------------------- + +PyX always needs a canvas to draw on. One possibility therefore consists in +creating a new canvas with :: + + c = canvas.canvas() + +and inserting the graphs into this canvas with ``c.insert(…)``. Here, ``…`` has +to be replaced by the name of the graph. Alternatively, the canvas created with +``graph.graphxy`` for one of the graphs can be used to insert the other graphs +even if they will be positioned outside the first graph. + +The second issue to address is positioning of the graphs. By specifying +``xpos`` and ``ypos`` when calling ``graphxy`` you can define the position of a +graph. Later on, the position and size of a graph ``g`` can be referred to as +``g.xpos`` ``g.ypos`` ``g.width`` and ``g.height`` even if for example the +height has never been specified explicitly but is only defined by a PyX +default. + +The following example shows how to put graph ``gupper`` above graph ``glower`` +on a canvas ``c``:: + + from pyx import * + from graph import graphxy + + c = canvas.canvas() + + glower = graphxy(width=10) + glower.plot(...) + c.insert(glower) + + gupper = graphxy(width=10, ypos=glower.ypos+glower.height+2) + gupper.plot(...) + + c.insert(gupper) + c.writeEPSfile(...) + +where ``…`` has to be replaced by the appropriate information like data and +symbol specifications and the name of the output file. Here, ``c.insert`` is +used to actually insert the subcanvasses for the graphs into the main canvas +``c`` and ``c.writeEPSfile`` in the last line requests to write the contents of +this canvas to a file. + + +How can I plot grid data? +------------------------- + +PyX offers support for plotting three-dimensional data as two-dimensional color +plots or grey-scale plots and of vector fields by providing ways to plot +rectangles and arrows in graphs. + +We start by considering the task of creating a two-dimensional color plot by +plotting a number of filled rectangles. One first needs to create a data set +which consists of five entries per data point. These are the lower left corner +(*x*\ :sub:`min`, *y*\ :sub:`min`) and the upper right corner (*x*\ :sub:`max`, +*y*\ :sub:`max`) of the triangle and a value between 0 and 1 determining the +color via a PyX color palette. The following code gives an idea of how to +proceed:: + + g.plot(graph.data.file("datafile.dat", xmin=1, xmax=2, ymin=3, ymax=4, color=5), + [graph.style.rect(color.palette.ReverseRainbow)] + ) + g.dodata() + +Here, we assume that the data are stored in ``datafile.dat`` and the columns +contain *x*\ :sub:`min`, *x* :sub:`max`, *y*\ :sub:`min`, *y*\ :sub:`max`, and +the color value in this order. The columns are numbered from 1, since the 0th +column contains the line number. To determine the color, we use the +``ReverseRainbow`` palette. The last line instructs PyX to plot the rectangles +before plotting the axes. Otherwise, the axes might be covered partially by the +rectangles and, in particular, the ticks might not be visible. Gray-scale plots +can easily be generated by specifying the palette ``Gray`` or ``ReverseGray`` +(cf. appendix C of the manual for a list of predefined palettes). + +At first sight, it seems surprising that plotting of grid data requires the +specification of four coordinates for the rectangle. The reason is that this +allows to draw rectangles of varying sizes which may help to reduce the size of +the postscript file by combining rectangles of the same color in horizontal or +vertical direction. For example, it may be sufficient to plot a grey-scale +image in a small number of grey shades and then combining rectangles may be +appropriate. Note, though, that this step is part of the data creation and not +preformed by PyX. Another advantage of fully specifying each rectangle is that +it is straightforward to leave parts of the graph blank. + +The same ideas as for the color plot can be applied to plot vector fields where +each data point is represented by an arrow. In this case a data point is +specified by the position of the arrow, its size and its direction as indicated +in the following code snippet:: + + g.plot(graph.data.file("datafile.dat"), x=1, y=2, size=3, angle=4), + [graph.style.arrow()] + ) + +Complete code examples can be found in ``examples/graphs/mandel.py`` and +``examples/graphs/arrows.py`` . + +.. _problemcoord: + +How can I access points in problem coordinates of a graph? +---------------------------------------------------------- + +Sometimes it may be necessary to add graphical elements to a graph in addition +to the data or function(s) which have been plotted as described in +:ref:`mingraphdata` and :ref:`mingraphfunc`. For a graph instance +``g`` the positioning can easily be done in canvas coordinates by making +use of the origin (``g.xpos``, ``g.ypos``) and the width +(``g.width``) and height (``g.height``) of the graph. + +Occasionally, it may be more convenient to specify the position of the +additional material in terms of problem coordinates. However, this requires +that the mapping from problem coordinates to canvas coordinates is known. By +default this is not the case before the content of the canvas is written to the +output which is too late for our purpose. One therefore needs to explicitly +instruct PyX to determine this mapping. One possibility is to ask PyX to finish +the graph by means of ``g.finish()``. Now, problem coordinates can be used to +insert additional material which will end up in front of the graph. If this is +not desired, one should only fix the layout of the graph by means of +``g.dolayout()``. Then, the additional material can be put onto the canvas +before the graph is drawn and it will therefore appear behind the graph. + +The conversion of problem coordinates (``px``, ``py``) to canvas coordinates +(``x``, ``y``) is performed as follows:: + + x, y = g.pos(px, py) + +By default, the problem coordinates will refer to the ranges of the *x* and *y* +axes. If several axes with different ranges exist, the instances of the desired +axes should be passed to the ``pos`` method by means of the keyword arguments +``xaxis`` and ``yaxis``. + +We remark that the drawing of lines parallel to one of the axes at specific +problem coordinates can also be done by adapting the method described in +:ref:`zeroline`. + +I would like a key for only some of my data sets. How do I do that? +------------------------------------------------------------------- + +.. todo:: + + This still needs to be answered. + +Axis properties +=============== + +How do I specify the tick increment? +------------------------------------ + +In the partition of a linear axis, the increments associated with ticks, +subticks etc. can be specified as argument of ``parter.linear``. In the +following example, ticks will be drawn at even values while subticks will be +drawn at all integers:: + + from pyx.graph import axis + + tg = graph.graphxy(width=10, + x=axis.linear(min=1, max=10, + parter=axis.parter.linear(tickdists=[2,1])) + ) + +.. _zeroline: + +How do I plot the zero line? +---------------------------- + +PyX releases before 0.6 offered the possibility to stroke a zero line by +specifying ``zeropathattrs`` in the painter constructor. In more recent +releases, one proceeds as follows. First one has to fix the layout information +of the graph by means of the ``finish`` or ``dolayout`` method (see +:ref:`problemcoord` for a more detailed explanation). Then, the +``xgridpath`` or ``ygridpath`` method of a graph will return a grid +path parallel to the *y* or *x* axis, respectively, at the specified *y* value. +As an example, a zero line in *x* direction can be drawn as follows:: + + g.finish() + g.stroke(g.ygridpath(0)) + +How can I add grid lines to a graph? +------------------------------------ + +Specifying ``gridattrs`` for the painter of an axis will generate grid lines +orthogonal to this axis. At least an empty list is needed like in :: + + g = graph.graphxy(width=10, + x=graph.axis.linear(painter=graph.axis.painter.regular(gridattrs=[])), + y=graph.axis.linear() + ) + +where grid lines in vertical direction are drawn in default style. + +Occassionally, one might want to draw grid lines corresponding to ticks and +subticks in a different style. This can be achieved by specifiying changeable +attributes using ``changelist``. The following code :: + + my_xpainter = graph.axis.painter.regular(gridattrs= + [attr.changelist([style.linestyle.solid, style.linestyle.dashed])] + ) + my_ypainter = graph.axis.painter.regular(gridattrs= + [attr.changelist([color.rgb.red, color.rgb.blue])] + ) + + g = graph.graphxy(width=10, + x=graph.axis.linear(painter=my_xpainter), + y=graph.axis.linear(painter=my_ypainter) + ) + +will create vertical solid and dashed grid lines for ticks and subticks, +respectively. The horizontal grid lines will be red for ticks and blue for +subticks. The changeable attributes are applied in a cyclic manner. Therefore, +in this example grid lines at subsubticks would be plotted in the same style as +for ticks. If this is not desired, the list of attributes should be extended by +an appropriate third style. The keyword ``None`` will switch off the respective +level of grid lines in case you want to draw them only e.g. for ticks but not +subticks. + +Data properties +=============== + +How do I choose the symbol and its attributes? +---------------------------------------------- + +Suppose a graph called ``g`` has been initialized, e.g. by using +``graph.graphxy``. Then, data and the style of their representation in the +graph are defined by calling ``g.plot`` like in the following example in which +filled circles are requested:: + + g.plot(graph.data.file("test.dat"), + [graph.style.symbol(graph.style.symbol.circle, symbolattrs=[deco.filled])] + ) + +As another example, if the linewidth of the symbol is too thin for your +purposes, you could use something like:: + + [graph.style.symbol(graph.style.symbol.plus, symbolattrs=[style.linewidth.Thick])] + +How do I choose the color of the symbols? +----------------------------------------- + +Colors are not properties of the symbol as such and can therefore not be +specified in ``symbolattrs`` directly. The color is rather related to the +plotting of the symbol as defined by ``deco.stroked`` or ``deco.filled``. +With :: + + graph.style.symbol(graph.style.symbol.circle, + symbolattrs=[deco.stroked([color.rgb.red]), + deco.filled([color.rgb.green])] + ) + +you will obtain a circle filled in green with a red borderline. + +How do I choose the line style? +------------------------------- + +If you do not want to use symbols, you can set the line style as in this +example :: + + g.plot(graph.data.file("test.dat"), + [graph.style.line([style.linewidth.Thin])] + ) + +where the linewidth is set. + +If you also want to use symbols, you can combine the symbol and the +line style as in :: + + g.plot(graph.data.file("test.dat"), + [graph.style.line(lineattrs=[style.linewidth.Thin, + style.linestyle.dashed]), + graph.style.symbol(graph.style.symbolline.circle, + symbolattrs=[deco.filled]) + ] + ) + +to plot the symbols on top of a thin, dashed line. You may alter the +order of the styles to plot the line on top of the symbols. + +How can I change the color of symbols or lines according to a palette? +---------------------------------------------------------------------- + +If several data sets should be plotted in different colors, one can specify in +``symbolattrs`` and/or ``lineattrs`` a palette like ``color.palette.Rainbow``. +Equidistant colors are chosen spanning the palette from one end to the other. +For example, for three data sets the colors are chosen from the palette at 0, +0.5, and 1. For the rainbow palette, this would correspond to red, green, and +blue, respectively. + +In the following example, symbols vary in form and change their color according +to the rainbow palette at the same time as the connecting lines:: + + mystyle = [graph.style.symbol(graph.style.symbol.changecircle, + symbolattrs=[color.palette.Rainbow]), + graph.style.line(lineattrs=[color.palette.Rainbow])] + +See question :ref:`changelist` for a more complete example demonstrating how to +use this style definition and for a comment on the necessity of defining +``mystyle`` (you are of course free to choose a different name). + +.. _changelist: + +How can I specify changing colors (or other attributes) for symbols or lines? +----------------------------------------------------------------------------- + +In ``symbolattrs`` and/or ``lineattrs`` so-called changelist can be used. As an +example :: + + mystyle = graph.style.symbol(symbolattrs= + [attr.changelist([color.rgb.red, color.rgb.green])]) + g.plot(graph.data.file("x.dat", x=1, y=2), [mystyle]) + g.plot(graph.data.file("y.dat", x=1, y=2), [mystyle]) + g.plot(graph.data.file("z.dat", x=1, y=2), [mystyle]) + +will switch between red and green symbols each time a new data set is plotted. +Several changelists can be specified. They are cycled independently and need +not be of the same length. It should be noted that the definition of +``mystyle`` in this example ensures that there is only one instance of the +definition of ``symbolattrs``. Putting an explicit definition of +``symbolattrs`` in each call to ``plot`` would not lead to the desired result +because each time a new instance would be created which then starts with the +first item in the changelist. + +It may be necessary to repeat attributes in order that several changelists +cooperate to produce the desired result. A common situation is that one would +like to cycle through a list of symbols which should be used in alternating +colors. This can be achieved with the following code:: + + mystyle = graph.style.symbol( + graph.style.symbol.changetriangletwice, + symbolattrs=[attr.changelist([color.rgb.red, color.rgb.green])]) + +which will produce a red triangle, a green triangle, a red circle, a green +circle and so on for diamond and square because ``changetriangletwice`` lists +each symbol twice. If instead of changing between colors one would like to +change between filled and open symbols, one can make use of a predefined +changelist :: + + mystyle = graph.style.symbol( + graph.style.symbol.changetriangletwice, + symbolattrs=[graph.style.symbol.changefilledstroked]) diff -Nru pyx-0.11.1/faq/python.rst pyx-0.12.1/faq/python.rst --- pyx-0.11.1/faq/python.rst 1970-01-01 00:00:00.000000000 +0000 +++ pyx-0.12.1/faq/python.rst 2012-10-16 20:56:07.000000000 +0000 @@ -0,0 +1,68 @@ +====== +Python +====== + +.. what_is_python: + +What is Python? +=============== + +From `www.python.org `_: + + Python is an *interpreted, interactive, object-oriented* programming + language. It is often compared to Tcl, Perl, Scheme or Java. + + Python combines remarkable power with very clear syntax. It has modules, + classes, exceptions, very high level dynamic data types, and dynamic typing. + There are interfaces to many system calls and libraries, as well as to various + windowing systems (X11, Motif, Tk, Mac, MFC). New built-in modules are easily + written in C or C++. Python is also usable as an extension language for + applications that need a programmable interface. + + The Python implementation is portable: it runs on many brands of UNIX, on + Windows, OS/2, Mac, Amiga, and many other platforms. If your favorite system + isn't listed here, it may still be supported, if there's a C compiler for it. + Ask around on `comp.lang.python `_ – or just + try compiling Python yourself. + + The Python implementation is `copyrighted `_ + but **freely usable and distributable, even for commercial use**. + +Where can I learn more about Python? +==================================== + +The place to start is `www.python.org `_ where you will +find plenty of information on Python including tutorials. + +What do I need to import in order to use PyX? +============================================= + +It is recommended to begin your Python code with:: + + from pyx import * + +when using PyX. This allows you for example to write simply ``graph.graphxy`` +instead of ``pyx.graph.graphxy``. The following modules will be loaded: +``attr``, ``box``, ``bitmap``, ``canvas``, ``color``, ``connector``, ``deco``, +``deformer``, ``document``, ``epsfile``, ``graph``, ``path``, ``pattern``, +``style``, ``trafo``, ``text``, and ``unit``. + +For convenience, you might import specific objects of a module like in:: + + from graph import graphxy + +which allows you to write ``graphxy()`` instead of ``graph.graphxy()``. + +All code segments in this document assume that the import line mentioned in the +first code snippet is present. + +What is a raw string and why should I know about it when using PyX? +=================================================================== + +The backslash serves in standard Python strings to start an escape sequence. +For example ``\n`` corresponds to a newline character. On the other hand, TeX +and LaTeX, which do the typesetting in PyX, use the backslash to indicate the +start of a command. In order to avoid the standard interpretation, the string +should be marked as a raw string by prepending it by an ``r`` like in:: + + c.text(0, 0, r"$\alpha\beta\gamma$") diff -Nru pyx-0.11.1/faq/pyxfaq.tex pyx-0.12.1/faq/pyxfaq.tex --- pyx-0.11.1/faq/pyxfaq.tex 2011-05-18 09:09:37.000000000 +0000 +++ pyx-0.12.1/faq/pyxfaq.tex 1970-01-01 00:00:00.000000000 +0000 @@ -1,1030 +0,0 @@ -% $Id: pyxfaq.tex 3007 2009-05-21 15:19:20Z gertingold $ -\documentclass[11pt,DIV14]{scrartcl} -\usepackage[latin1]{inputenc} -\usepackage{url} -\usepackage{mathptmx} -%\usepackage[all,comments]{glifaq} -\usepackage[answered]{glifaq} -\usepackage[pdftex]{hyperref} -\hypersetup{pdftitle={PyX FAQ}% - ,pdfauthor={\textcopyright\ Gert-Ludwig Ingold - }% - ,colorlinks=true% - ,linkcolor=blue} -\def\pyxversion{\input{pyxversion}} -\begin{document} - -\begin{center} -\LARGE\sffamily Some frequently and\\ -not so frequently asked questions\\ -about \PyX -\par -{\small\sffamily (version \pyxversion)}\\[1truecm] -\large -Gert-Ludwig Ingold \par -\href{mailto:gert.ingold@physik.uni-augsburg.de}{\url{}} -\end{center} -\toc - -\vspace{2truecm} -\section*{Acknowledgements} -The following persons have in one way or the other, e.g.\ by asking good -questions or providing answers, contributed to this FAQ:\\ -Walter Brisken, Alejandro Gaita-Arinyo, Pierre Joyot, Jrg Lehmann, -John Owens, Michael Schindler, Gerhard Schmid, Andr{\'e} Wobst. -\newpage - -\section{General aspects of \PyX} -\question{a}{The name of the game} -{} -{Originally, the name \PyX{} was constructed as a combination of -\textbf{P}ostscript, i.e.\ the first output format supported by \PyX{}, -P\textbf{y}thon, i.e.\ the language in which \PyX{} is written, and -Te\textbf{X}, i.e.\ the program which \PyX{} uses for typesetting purposes. -Actually, the title of this question is a tribute to \TeX{} because it is -taken from the first chapter of the \TeX{}book\footnote{D.~Knuth, \textit{The -\TeX{}book} (Addison-Wesley, 1984)} where the origin of the name \TeX{} and its -pronunciation are explained. - -Despite the ties between \TeX{} and \PyX{}, their pronunciation is quite -different. According to the developers of \PyX{}, it should be pronounced as -\tipagraph{[pYks]}{tipapyks}. Please do not pronounce it as -\tipagraph{[pYx]}{tipapyx} or \tipagraph{[pY\c c]}{tipapych}. -} - -\question{a}{Where do I get the latest version of \PyX?} -{} -{\label{q:where_do_I_get_PyX} -The current release of \PyX{} (as well as older ones) is freely available -from \url{http://pyx.sourceforge.net} where also a CVS repository with the -latest patches can be found. Possibly older versions of \PyX{} are -also available as package for -various Linux distributions: see, for instance, -\url{http://packages.debian.org/testing/python/python-pyx.html} for -information on the \PyX{} package in Debian GNU/Linux, -\url{http://packages.gentoo.org/ebuilds/?pyx-0.7.1} for a Gentoo Linux -ebuild, and -\url{http://www.novell.com/products/linuxpackages/professional/python-pyx.html} -for the \PyX{} package in the SUSE LINUX professional distribution. -} - -\question{c}{How can I determine the version of \PyX{} running on my -machine?} -{} -{Start a python session (usually by typing \texttt{python} at the system -prompt) and then type the following two commands (\texttt{>>>} is the python -prompt) -\begin{progcode} ->>> import pyx\\ ->>> pyx.\us\us{}version\us\us -\end{progcode} -} - -\question{a}{How can I access older versions of \PyX?} -{} -{As at present it is not guaranteed that \PyX{} is backward compatible, it may -be desirable to access an older version of \PyX{} instead of adapting older -code to the current version of \PyX. In order to do that, one needs the -corresponding \PyX{} package (see \uaref{q:where_do_I_get_PyX} if you need to -download it), which should be unpacked below a directory, e.g.\ -\texttt{/home/xyz/Python}, where you want to keep the various \PyX{} versions. -This will result in a subdirectory with a name like \texttt{PyX-0.8} which -contains the contents of the corresponding package. You can then ask Python to -first look in the appropriate directory before looking for the current version -of \PyX{} by inserting the following code (appropriately modified according -to your needs) at the beginning of your program before importing the \PyX{} -module: -\begin{progcode} -import sys\\ -sys.path.insert(0, "/home/xyz/Python/PyX-0.8") -\end{progcode} -Including appropriate lines even if the current version of \PyX{} is used, -might turn out to be helpful when the current version has become an old -version (unless you have no difficulties determining the \PyX{} version by -looking at your code). - -If your operating system supports path expansion, you might use as an -alternative: -\begin{progcode} -import sys, os\\ -sys.path.insert(0, os.path.expanduser("\char126/Python/PyX-0.8")) -\end{progcode} -which will expand the tilde to your home directory. -} - -\question{c}{Does \PyX{} run under my favorite operating system?} -{} -{Yes, if you have installed Python (\uaref{q:what_is_python}) -and \TeX{} (\uaref{q:what_is_tex}). Both are available for -a large variety of operating systems so chances are pretty good that you will -get \PyX{} to work on your system. -} - -\question{c}{Under which versions of Python will \PyX{} run?} -{} -{\PyX{} is supposed to work with Python 2.1 and above. However, most of the -development takes place under the current production version of Python -(2.4.1 by the time of this writing) and thus \PyX{} is better tested -with this version. On the other hand, the examples and tests are -verified to run with Python 2.1 and above using the latest bugfix -releases. \PyX{} will not work with earlier Python versions due -to missing language features. - -The version of your Python interpreter can be determined by calling -it with the option \texttt{-V}. Alternatively, you can simply start the -interpreter and take a look at the startup message. Note that there may be -different versions of Python installed on your system at the same time. The -default Python version need not be the same for all users. -} - -\question{a}{Does \PyX{} provide a GUI to view the produced image?} -{} -{No, \PyX{} itself does not provide a means to view the produced image. The -result of a \PyX{} run is an EPS (= Encapsulated PostScript) file, a -PS (= PostScript) file or a PDF (= Portable Document Format) file, which can -be viewed, printed or imported into other applications. - -There are several means of viewing PS and EPS files. A common way -would be to use \texttt{ghostview} which provides a user interface to -the PostScript interpreter \texttt{ghostscript}. More information -about this software, which is available for a variety of platforms, -can be found at \url{http://www.cs.wisc.edu/~ghost/}. If you do not -own a printer which is capable of printing PostScript files directly, -\texttt{ghostscript} may also be useful to translate PS and EPS files -produced by \PyX{} into something your printer will understand. - -PDF files can be viewed by means of the \texttt{Adobe -Reader\textsuperscript{\textregistered}} -available from -\url{http://www.adobe.com/products/acrobat/readstep2.html}. On systems -running X11, \texttt{xpdf} might be an alternative. It is available from -\url{http://www.foolabs.com/xpdf/}.} - -\question{a}{I am a Gnuplot user and want to try \PyX. Where can I get some -help?} -{} -{ -There exists a tutorial by Titus Winters which explains how to perform -standard Gnuplot tasks with \PyX. The tutorial can be found at -\url{http://www.cs.ucr.edu/~titus/pyxTutorial/}. -} - -\question{a}{Where can I get help if my question is not answered in this -FAQ?} -{} -{The \PyX{} sources contain a reference manual which is also available -online at \url{http://pyx.sourceforge.net/manual/}. Furthermore, there -exists a set of examples demonstrating various features of \PyX, which is -available in the sources or can be browsed at \url{http://pyx.sourceforge.net/examples.html}. -If the feature you are looking for is among them, using the appropriate part -of the example code or adapting it for your purposes may help. - -There is also a user discussion list about \PyX{} which you can subscribe to -at \url{http://lists.sourceforge.net/lists/listinfo/pyx-user}. The archive of -the discussion list is available at \url{http://sourceforge.net/mailarchive/forum.php?forum_name=pyx-user}. - -Finally, it might be worth checking \url{http://pyx.sourceforge.net/pyxfaq.pdf} -for an updated version of this FAQ. -} - -\section{Python} - -\question{c}{What is Python?} -{} -{\label{q:what_is_python} -From \url{www.python.org}: -\begin{quote} -Python is an \textit{interpreted, interactive, object-oriented} programming -language. It is often compared to Tcl, Perl, Scheme or Java. - -Python combines remarkable power with very clear syntax. It has modules, -classes, exceptions, very high level dynamic data types, and dynamic typing. -There are interfaces to many system calls and libraries, as well as to various -windowing systems (X11, Motif, Tk, Mac, MFC). New built-in modules are easily -written in C or C++. Python is also usable as an extension language for -applications that need a programmable interface. - -The Python implementation is portable: it runs on many brands of UNIX, on -Windows, OS/2, Mac, Amiga, and many other platforms. If your favorite system -isn't listed here, it may still be supported, if there's a C compiler for it. -Ask around on \href{news:comp.lang.python}{news:comp.lang.python} --- or just -try compiling Python yourself. - -The Python implementation is -\href{http://www.python.org/doc/Copyright.html}{copyrighted} -but \textbf{freely usable and distributable, even for commercial use}. -\end{quote} -} - -\question{a}{Where can I learn more about Python?} -{} -{The place to start is \url{www.python.org} where you will find plenty of -information on Python including tutorials. -} - -\question{c}{What do I need to import in order to use \PyX?} -{} -{It is recommended to begin your Python code with -\begin{progcode} -from pyx import * -\end{progcode} -when using \PyX. This allows you for example to write simply -\texttt{graph.graphxy} -instead of \texttt{pyx.graph.graphxy}. The following modules will be loaded: -\texttt{attr}, \texttt{box}, \texttt{bitmap}, \texttt{canvas}, \texttt{color}, \texttt{connector}, -\texttt{deco}, \texttt{deformer}, \texttt{document}, \texttt{epsfile}, \texttt{graph}, \texttt{path}, -\texttt{pattern}, \texttt{style}, \texttt{trafo}, \texttt{text}, and \texttt{unit}. - -For convenience, you might import specific objects of a module like in -\begin{progcode} -from graph import graphxy -\end{progcode} -which allows you to write \texttt{graphxy()} instead of \texttt{graph.graphxy()}. - -All code segments in this document assume that the import line mentioned in -the first code snippet is present. -} - -\question{a}{What is a raw string and why should I know about it when - using \PyX?} -{} -{\label{q:raw_string} -The backslash serves in standard Python strings to start an escape sequence. -For example {\cs n} corresponds to a newline character. On the other hand, -\TeX{} and \LaTeX{}, which do the typesetting in \PyX, use the backslash to -indicate the start of a command. In order to avoid the standard interpretation, -the string should be marked as a raw string by prepending it by an \texttt{r} -like in -\begin{progcode} -c.text(0, 0, r"\${\cs alpha}{\cs beta}{\cs gamma}\$") -\end{progcode} -} - -\section{General aspects of plotting with \PyX} - -\question{a}{How do I generate multipage output?} -{} -{ -With versions 0.8 and higher it is possible to produce multipage output, -i.e. a Postscript or PDF file containing more than one page. In order to -achieve this, one creates pages by drawing on a canvas as usual and -appends them in the desired order to a document from which Postscript or -PDF output is produced. The following example serves as an illustration: -\begin{progcode} -from pyx import *\\ -\\ -d = document.document()\\ -for i in range(3):\\ -~~~~c = canvas.canvas()\\ -~~~~c.text(0, 0, "page \%i" \% (i+1))\\ -~~~~d.append(document.page(c, paperformat=document.paperformat.A4,\\ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~margin=3*unit.t\_cm,\\ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~fittosize=1))\\ -d.writePSfile("multipage") -\end{progcode} -Here, \texttt{d} is the document into which pages are inserted by means -of the \texttt{append} method. When converting from a canvas to a document -page, the page properties like the paperformat are specified. In the last -line, output is produced from document \texttt{d}. -} - -\section{Plotting of graphs} - -\subsection{General aspects} - -\question{c}{How do I generate a graph from data as simply as possible?} -{} -{\label{q:mingraphdata} -Suppose that you have a data file \texttt{x.dat} containing values for -$x$ and $y$ in two columns. Then the following code will do the job -\begin{progcode} -from pyx import *\\ -\\ -g = graph.graphxy(width=10)\\ -g.plot(graph.data.file("x.dat", x=1, y=2))\\ -g.writeEPSfile("x") -\end{progcode} -\texttt{graphxy} creates a canvas (called \texttt{g} in this example) onto -which the graph will be drawn and it sets the default behavior including the -axis. There is, however, no default value for the width of the graph. In -\texttt{plot} you have to specify the name of the data file and the columns -from which the data should be taken. Finally, \texttt{writeEPSfile} will -generate the postscript file \texttt{x.eps} which you can view or print. - -A minimal example is also provided in the \PyX{} distribution as -\path{examples/graphs/minimal.py}. -} - -\question{a}{How do I generate a graph of a function as simply as possible?} -{} -{\label{q:mingraphfunc} -The following example will draw a parabola: -\begin{progcode} -from pyx import *\\ -\\ -g = graph.graphxy(width=10,\\ -~~~~~~~~~~~~~~~~~~x=graph.axis.linear(min=-2, max=2)\\ -~~~~~~~~~~~~~~~~~~)\\ -\\ -g.plot(graph.data.function("y(x)=x**2"))\\ -\\ -g.writeEPSfile("x") -\end{progcode} -Most of the code has been explained in \uaref{q:mingraphdata}. The main -difference is that here you need to specify minimum and maximum for the -$x$-axis so that \PyX{} knows in which range to evaluate the function. - -Another, slightly more complex, example is also provided in the \PyX{} -distribution as \path{examples/graphs/piaxis.py}. -} - -\question{a}{How can I stack graphs?} -{} -{\PyX{} always needs a canvas to draw on. One possibility therefore consists -in creating a new canvas with -\begin{progcode} -c = canvas.canvas() -\end{progcode} -and inserting the graphs into this canvas with \texttt{c.insert(...)}. Here, -\texttt{...} has to be replaced by the name of the graph. Alternatively, the -canvas created with \texttt{graph.graphxy} for one of the graphs can be used -to insert the other graphs even if they will be positioned outside the -first graph. - -The second issue to address is positioning of the graphs. By specifying -\texttt{xpos} and \texttt{ypos} when calling \texttt{graphxy}, you can -define the position of a graph. Later on, the position and size of a -graph \texttt{g} can be referred to as \texttt{g.xpos}, \texttt{g.ypos}, -\texttt{g.width}, and \texttt{g.height} even if for example the height has -never been specified explicitly but is only defined by a \PyX{} default. - -The following example shows how to put graph \texttt{gupper} above graph -\texttt{glower} on a canvas \texttt{c}: -\begin{progcode} -from pyx import *\\ -from graph import graphxy\\ -\\ -c = canvas.canvas()\\ -\\ -glower = graphxy(width=10)\\ -glower.plot(...)\\ -c.insert(glower)\\ -\\ -gupper = graphxy(width=10, ypos=glower.ypos+glower.height+2)\\ -gupper.plot(...)\\ -\\ -c.insert(gupper)\\ -c.writeEPSfile(...) -\end{progcode} -where \texttt{...} has to be replaced by the appropriate information like -data and symbol specifications and the name of the output file. Here, -\texttt{c.insert} is used to actually insert the subcanvasses -for the graphs into the main canvas \texttt{c} and \texttt{c.writeEPSfile} -in the last line requests to write the contents of this canvas to a file. - -%In order to suppress the labels of the $x$-axis of the upper graph, use -%\begin{progcode} -%myaxispainter = graph.axispainter(labelattrs=None) -% -%gupper = graph.graphxy(..., -% x=graph.axis.linear(..., -% part=graph.linpart(), -% painter=myaxispainter) -% ) -%\end{progcode} -} - -\question{a}{How can I plot grid data?} -{} -{\PyX{} offers support for plotting three-dimensional data as two-dimensional -color plots or grey-scale plots and of vector fields by providing ways to -plot rectangles and arrows in graphs. - -We start by considering the task of creating a two-dimensional color plot by -plotting a number of filled rectangles. One first needs to create a data set -which consists of five entries per data point. These are the lower left corner -$(x_\mathrm{min},y_\mathrm{min})$ and the upper right corner -$(x_\mathrm{max},y_\mathrm{max})$ of the triangle and a value between 0 and 1 -determining the color via a \PyX{} color palette. The following code gives an -idea of how to proceed: -\begin{progcode} -g.plot(graph.data.file("datafile.dat", xmin=1, xmax=2, ymin=3, ymax=4, color=5),\\ -~~~~~~~[graph.style.rect(color.palette.ReverseRainbow)]\\ -~~~~~~)\\ -g.dodata() -\end{progcode} -Here, we assume that the data are stored in \texttt{datafile.dat} and the -columns contain $x_\mathrm{min}$, $x_\mathrm{max}$, $y_\mathrm{min}$, -$y_\mathrm{max}$, and the color value in this order. The columns are -numbered from 1, since the 0th column contains the line number. To -determine the color, we use the \texttt{ReverseRainbow} palette. The -last line instructs \PyX{} to plot the rectangles before plotting the -axes. Otherwise, the axes might be covered partially by the rectangles -and, in particular, the ticks might not be visible. Gray-scale plots -can easily be generated by specifying the palette \texttt{Gray} or -\texttt{ReverseGray} (cf.\ appendix C of the manual for a list of -predefined palettes). - -At first sight, it seems surprising that plotting of grid data requires -the specification of four coordinates for the rectangle. The reason is that -this allows to draw rectangles of varying sizes which may help to reduce the -size of the postscript file by combining rectangles of the same color in -horizontal or vertical direction. For example, it may be sufficient to plot -a grey-scale image in a small number of grey shades and then combining -rectangles may be appropriate. Note, though, that this step is part of the -data creation and not preformed by \PyX{}. Another advantage of fully -specifying each rectangle is that it is straightforward to leave parts of the -graph blank. - -The same ideas as for the color plot can be applied to plot vector fields where -each data point is represented by an arrow. In this case a data point is -specified by the position of the arrow, its size and its direction as indicated -in the following code snippet: -\begin{progcode} -g.plot(graph.data.file("datafile.dat"), x=1, y=2, size=3, angle=4),\\ -~~~~~~~[graph.style.arrow()]\\ -~~~~~~) -\end{progcode} - -Complete code examples can be found in \path{examples/graphs/mandel.py} and -\path{examples/graphs/arrows.py}\,. -} - -\question{a}{How can I access points in problem coordinates of a graph?} -{} -{\label{q:problemcoord} -Sometimes it may be necessary to add graphical elements to a graph in addition -to the data or function(s) which have been plotted as described in -\uaref{q:mingraphdata} and \uaref{q:mingraphfunc}. For a graph instance -\texttt{g} the positioning can easily be done in canvas coordinates by making -use of the origin (\texttt{g.xpos}, \texttt{g.ypos}) and the width -(\texttt{g.width}) and height (\texttt{g.height}) of the graph. - -Occasionally, it may be more convenient to specify the position of the -additional material in terms of problem coordinates. However, this requires -that the mapping from problem coordinates to canvas coordinates is known. -By default this is not the case before the content of the canvas is written -to the output which is too late for our purpose. One therefore needs to -explicitly instruct \PyX{} to determine this mapping. One possibility is to -ask \PyX{} to finish the graph by means of \texttt{g.finish()}. Now, problem -coordinates can be used to insert additional material which will end up in -front of the graph. If this is not desired, one should only fix the layout -of the graph by means of \texttt{g.dolayout()}. Then, the additional material -can be put onto the canvas before the graph is drawn and it will therefore -appear behind the graph. - -The conversion of problem coordinates (\texttt{px}, \texttt{py}) to canvas -coordinates (\texttt{x}, \texttt{y}) is performed as follows: -\begin{progcode} -x, y = g.pos(px, py) -\end{progcode} -By default, the problem coordinates will refer to the ranges of the $x$ and $y$ -axes. If several axes with different ranges exist, the -instances of the desired axes should be passed to the \texttt{pos} method by -means of the keyword arguments \texttt{xaxis} and \texttt{yaxis}. - -We remark that the drawing of lines parallel to one of the axes at specific -problem coordinates can also be done by adapting the method described in -\uaref{q:zeroline}. -} - -\question{t}{I would like a key for only some of my data sets. How do I do -that?} -{} -{ -} - -\subsection{Axis properties} - -\question{a}{How do I specify the tick increment?} -{} -{In the partition of a linear axis, the increments associated with ticks, -subticks etc.\ can be specified as argument of \texttt{parter.linear}. In the -following example, ticks will be drawn at even values while subticks will -be drawn at all integers: -\begin{progcode} -from pyx.graph import axis\\ -tg = graph.graphxy(width=10,\\ -~~~~~~~~~~~~~~~~~~~x=axis.linear(min=1, max=10,\\ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~parter=axis.parter.linear(tickdists=[2,1]))\\ -~~~~~~~~~~~~~~~~~~~) -\end{progcode} -} - -\question{a}{How do I plot the zero line?} -{} -{ -\label{q:zeroline} -\PyX{} releases before 0.6 offered the possibility to stroke a zero line by -specifying \texttt{zeropathattrs} in the painter constructor. In more recent -releases, one proceeds as follows. First one has to fix the layout information -of the graph by means of the \texttt{finish} or \texttt{dolayout} method (see -\ref{q:problemcoord} for a more detailed explanation). Then, the -\texttt{xgridpath} or \texttt{ygridpath} method of a graph will return a grid -path parallel to the $y$ or $x$ axis, respectively, at the specified $y$ value. -As an example, a zero line in $x$ direction can be drawn as follows: -\begin{progcode} -g.finish()\\ -g.stroke(g.ygridpath(0)) -\end{progcode} -} - -\question{a}{How can I add grid lines to a graph?} -{} -{ -Specifying \texttt{gridattrs} for the painter of an axis will generate grid -lines orthogonal to this axis. At least an empty list is needed like in -\begin{progcode} -g = graph.graphxy(width=10,\\ -~~~~~~~~~~~~~~~~~~x=graph.axis.linear(painter=graph.axis.painter.regular(gridattrs=[])),\\ -~~~~~~~~~~~~~~~~~~y=graph.axis.linear()\\ -~~~~~~~~~~~~~~~~~~) -\end{progcode} -where grid lines in vertical direction are drawn in default style. - -Occassionally, one might want to draw grid lines corresponding to ticks and -subticks in a different style. This can be achieved by specifiying -changeable attributes using \texttt{changelist}. The following code -\begin{progcode} -my\_xpainter = graph.axis.painter.regular(gridattrs=\\ -~~~~~~~~~~~~~~~~~~~~[attr.changelist([style.linestyle.solid, style.linestyle.dashed])]\\ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~)\\ -my\_ypainter = graph.axis.painter.regular(gridattrs=\\ -~~~~~~~~~~~~~~~~~~~~[attr.changelist([color.rgb.red, color.rgb.blue])]\\ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~)\\ -\\ -g = graph.graphxy(width=10,\\ -~~~~~~~~~~~~~~~~~~x=graph.axis.linear(painter=my\_xpainter),\\ -~~~~~~~~~~~~~~~~~~y=graph.axis.linear(painter=my\_ypainter)\\ -~~~~~~~~~~~~~~~~~~) -\end{progcode} -will create vertical solid and dashed grid lines for ticks and subticks, -respectively. The horizontal grid lines will be red for ticks and blue for -subticks. The changeable attributes are applied in a cyclic manner. Therefore, -in this example grid lines at subsubticks would be plotted in the same style -as for ticks. If this is not desired, the list of attributes should be extended -by an appropriate third style. The keyword \texttt{None} will switch off -the respective level of grid lines in case you want to draw them only e.g.\ -for ticks but not subticks. -} - -\subsection{Data properties} - -\question{a}{How do I choose the symbol and its attributes?} -{} -{\label{q:choose_symbol} -Suppose a graph called \texttt{g} has been initialized, e.g.\ by using -\texttt{graph.graphxy}. Then, data and the style of their representation -in the graph are defined by calling \texttt{g.plot} like in the following -example in which filled circles are requested: -\begin{progcode} -g.plot(graph.data.file("test.dat"),\\ -~~~~~~~[graph.style.symbol(graph.style.symbol.circle, symbolattrs=[deco.filled])]\\ -~~~~~~~) -\end{progcode} -As another example, if the linewidth of the symbol is too thin for your -purposes, you could use something like: -\begin{progcode} -[graph.style.symbol(graph.style.symbol.plus, - symbolattrs=[style.linewidth.Thick])]\\ -\end{progcode} -} - -\question{a}{How do I choose the color of the symbols?} -{} -{Colors are not properties of the symbol as such and can therefore not -be specified in \texttt{symbolattrs} directly. The color is rather related -to the plotting of the symbol as defined by \texttt{deco.stroked} or -\texttt{deco.filled}. With -\begin{progcode} -graph.style.symbol(graph.style.symbol.circle,\\ -~~~~~~~~~~~~~~~~~~~symbolattrs=[deco.stroked([color.rgb.red]),\\ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~deco.filled([color.rgb.green])]\\ -~~~~~~~~~~~~~~~~~~~) -\end{progcode} -you will obtain a circle filled in green with a red borderline. -} - -\question{a}{How do I choose the line style?} -{} -{If you do not want to use symbols, you can set the line style as in this -example -\begin{progcode} -g.plot(graph.data.file("test.dat"),\\ -~~~~~~~[graph.style.line([style.linewidth.Thin])]\\ -~~~~~~~) -\end{progcode} -where the linewidth is set. - -If you also want to use symbols, you can combine the symbol and the -line style as in -\begin{progcode} -g.plot(graph.data.file("test.dat"),\\ -~~~~~~~[graph.style.line(lineattrs=[style.linewidth.Thin,\\ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~style.linestyle.dashed]),\\ -~~~~~~~~graph.style.symbol(graph.style.symbolline.circle,\\ -~~~~~~~~~~~~~~~~~~~~~~~~~~~symbolattrs=[deco.filled]) -~~~~~~~] -~~~~~~) -\end{progcode} -to plot the symbols on top of a thin, dashed line. You may alter the -order of the styles to plot the line on top of the symbols. -} - -\question{a}{How can I change the color of symbols or lines according to a -palette?} -{} -{If several data sets should be plotted in different colors, one can specify -in \texttt{symbolattrs} and/or \texttt{lineattrs} a palette like -\texttt{color.palette.Rainbow}. Equidistant colors are chosen spanning the -palette from one end to the other. For example, for three data sets the -colors are chosen from the palette at $0., 0.5,$ and $1$. For the rainbow -palette, this would correspond to red, green, and blue, respectively. - -In the following example, symbols vary in form and change their color according -to the rainbow palette at the same time as the connecting lines: -\begin{progcode} -mystyle = [graph.style.symbol(graph.style.symbol.changecircle,\\ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~symbolattrs=[color.palette.Rainbow]),\\ -~~~~~~~~~~~graph.style.line(lineattrs=[color.palette.Rainbow])] -\end{progcode} -See question~\ref{q:changelist} for a more complete example demonstrating -how to use this style definition and for a comment on the necessity of -defining \texttt{mystyle} (you are of course free to choose a different name). -} - -\question{a}{How can I specify changing colors (or other attributes) for -symbols or lines?} -{} -{\label{q:changelist} -In \texttt{symbolattrs} and/or \texttt{lineattrs} so-called changelist can -be used. As an example -\begin{progcode} -mystyle = graph.style.symbol(symbolattrs=\\ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~[attr.changelist([color.rgb.red, color.rgb.green])])\\ -g.plot(graph.data.file("x.dat", x=1, y=2), [mystyle])\\ -g.plot(graph.data.file("y.dat", x=1, y=2), [mystyle])\\ -g.plot(graph.data.file("z.dat", x=1, y=2), [mystyle]) -\end{progcode} -will switch between red and green symbols each time a new data set is -plotted. Several changelists can be specified. They are cycled independently -and need not be of the same length. It should be noted that the definition of -\texttt{mystyle} in this example ensures that there is only one instance of -the definition of \texttt{symbolattrs}. Putting an explicit definition of -\texttt{symbolattrs} in each call to \texttt{plot} would not lead to the -desired result because each time a new instance would be created which then -starts with the first item in the changelist. - -It may be necessary to repeat attributes -in order that several changelists cooperate to produce the desired result. -A common situation is that one would like to cycle through a list of symbols -which should be used in alternating colors. This can be achieved with -the following code: -\begin{progcode} -mystyle = graph.style.symbol(\\ -~~~~~~~~~~~~~~~~graph.style.symbol.changetriangletwice,\\ -~~~~~~~~~~~~~~~~symbolattrs=[attr.changelist([color.rgb.red, color.rgb.green])]) -\end{progcode} -which will produce a red triangle, a green triangle, a red circle, a green -circle and so on for diamond and square because \texttt{changetriangletwice} -lists each symbol twice. If instead of changing between colors -one would like to change between filled and open symbols, one can make use of -a predefined changelist -\begin{progcode} -mystyle = graph.style.symbol(\\ -~~~~~~~~~~~~~~~~graph.style.symbol.changetriangletwice,\\ -~~~~~~~~~~~~~~~~symbolattrs=[graph.style.symbol.changefilledstroked]) -\end{progcode} -} - -\section{Other plotting tasks} - -\question{a}{How can I rotate text?} -{} -{Text can be written at an arbitrary angle by specifying the appropriate -transformation as an attribute. The command -\begin{progcode} -c.text(0, 0, "Text", [trafo.rotate(60)]) -\end{progcode} -will write at an angle of 60 degrees relative to the horizontal axis. If no -pivot is specified (like in this example), the text is rotated around the -reference point given in the first two arguments of \texttt{text}. In the -following example, the pivot coincides with the center of the text: -\begin{progcode} -c.text(0, 0, "Text", [text.halign.center,text.valign.middle,trafo.rotate(60)]) -\end{progcode} -} - -\question{a}{How can I clip a canvas?} -{} -{In order to use only a part of a larger canvas, one may want to clip it. This -can be done by creating a clipping object which is used when creating a canvas -instance: -\begin{progcode} -clippath = path.circle(0.,0.,1.)\\ -clipobject = canvas.clip(clippath)\\ -c = canvas.canvas([clipobject]) -\end{progcode} -In this example, the clipping path used to define the clipping object is a -circle. -} -\section{\TeX{} and \LaTeX{}} - -\subsection{General aspects} - -\question{a}{What is \TeX/\LaTeX{} and why do I need it?} -{} -{\label{q:what_is_tex} -\TeX{} is a high quality typesetting system developed by Donald E. Knuth -which is available for a wide variety of operating systems. \LaTeX{} is a -macro package originally developed by Leslie Lamport which makes life with -\TeX{} easier, in particular for complex typesetting tasks. The current -version of \LaTeX{} is referred to as \LaTeXe{} and offers e.g.\ improved -font selection as compared to the older \LaTeX{} 2.09 which should no longer -be used. - -All typesetting tasks in \PyX{} are finally handed over to \TeX{} (which is the -default) or \LaTeX{}, so that \PyX{} cannot do without it. On the other hand, -the capabilities of \TeX{} and \LaTeX{} can be used for complex tasks where -both graphics and typesetting are needed. -} - -\question{a}{I don't know anything about \TeX{} and \LaTeX{}. Where can I read -something about it?} -{} -{\label{q:intro_tex_latex} -Take a look at CTAN (\uaref{q:ctan}) where in \ctan{info} -you may be able to find some useful information. There exists for example -``A Gentle Introduction to \TeX'' by M.~Doob (\ctan{gentle/gentle.pdf}) and -``The Not So Short Introduction to \LaTeXe'' -(\ctan{info/lshort/english/lshort.pdf}) by T.~Oetiker et al. The latter has -been translated into a variety of languages among them korean (which you will -not be able to read unless you have appropriate fonts installed) and mongolian. - -Of course, it is likely that these documents will go way beyond what you -will need for generating graphics with \PyX{} so you don't have to read all -of it (unless you want to use \TeX{} or \LaTeX{} for typesetting which can be -highly recommended). - -There exists also a number of FAQs on \TeX{} at \ctan{help}. -} - -\question{a}{What is CTAN?} -{} -{\label{q:ctan} -CTAN is the Comprehensive TeX Archive Network where you will find almost -everything related to \TeX{} and friends. The main CTAN servers are -\url{tug.ctan.org}, \url{dante.ctan.org}, and \url{cam.ctan.org}. A list of -FTP mirrors can be found at \ctan{CTAN.sites}. - -In this FAQ, \texttt{CTAN:} refers to the root of an anonymous ftp CTAN tree, -e.g.\ \url{ftp://ctan.tug.org/tex-archive/}, -\url{ftp://ftp.dante.de/tex-archive/}, -and \url{ftp://ftp.tex.ac.uk/tex-archive/}. The links to CTAN in this document -point to one of these servers but you might consider using a FTP mirror closer -to you in order to reduce traffic load. -} - -\question{a}{Is there support for Con\TeX{}t?} -{} -{No, and as far as I know there no plans to provide it in the near future. -Given the close ties between Con\TeX{}t and Meta\-Post, Con\TeX{}t users -probably prefer to stick with the latter anyway. -} - -\subsection{\TeX{} and \LaTeX{} commands useful for \PyX} - -\question{a}{How do I get a specific symbol with \TeX{} or \LaTeX?} -{} -{A list of mathematical symbols together with the appropriate command name -can be found at \ctan{info/symbols/math/symbols.ps}. A comprehensive list -containing more than 2500 symbols for use with \LaTeX{} can be obtained from -\ctan{info/symbols/comprehensive/symbols-a4.pdf}. In some cases it might be -necessary to install fonts or packages available from CTAN -(\uaref{q:ctan}). -} - -\subsection{\TeX{} and \LaTeX{} errors} - -\question{a}{Undefined control sequence \cs{usepackage}} -{} -{\label{q:undefined_usepackage} -The command \cs usepackage is specific to \LaTeX{}. Since by default \PyX{} -uses \TeX{}, you have to specify the correct mode: -\begin{progcode} -text.set(mode="latex") -\end{progcode} -} - -\question{a}{Undefined control sequence \cs{frac}} -{} -{\label{q:undefined_frac} -The command \cs frac is only available in \LaTeX{}. In \TeX{} you should -use \texttt{\cb{a\cs over b}} in math mode to produce ${a\over b}$. As an -alternative you may ask for the \LaTeX{} mode as explained in -\ref{q:undefined_usepackage}. -} - -\question{a}{Missing \$ inserted} -{} -{You have specified \TeX- or \LaTeX-code which is only valid in math mode. -Typical examples are greek symbols, sub- and superscripts or fractions. - -On the \PyX{} level, you can specify math mode for the whole string by using -\texttt{text.mathmode} as in -\begin{progcode} -c.text(0, 0, r"{\cs alpha}", text.mathmode) -\end{progcode} -Keep also in mind that the standard Python interpretation of the backslash as -introducing escape sequences needs to be prevented -\uaref{q:raw_string}. - -On the \TeX/\LaTeX{} level you should enclose the commands requiring math -mode in \$'s. As an example, \texttt{\$\cs alpha\us i\hat j\$} will produce -$\alpha_i^j$. This allows you to specify math mode also for substrings. There -exist other ways to specify math mode in \TeX{} and \LaTeX{} which are -particularly useful for more complex typesetting tasks. To learn more about -it, you should consult the documentation -\uaref{q:intro_tex_latex}. -} - -\question{a}{Why do environments like itemize or eqnarray seem not to work?} -{} -{An itemize environment might result in a \LaTeX{} error complaining about -a ``\texttt{missing \cs item}'' or an eqnarray might lead to a \LaTeX{} message -``\texttt{missing \cs endgroup inserted}'' even though the syntax appears to be -correct. The \TeX{}nical reason is that in \PyX{} text is typeset in left-right -mode (LR mode) which does not allow linebreaks to occur. There are two ways out. - -If the text material should go in a box of given width, a parbox can be used -like in the following example: -\begin{progcode} -text.set(mode="latex")\\ -c = canvas.canvas()\\ -w = 2\\ -c.text(0, 0, r"\cs begin\cb{itemize}\cs item a\cs item b\cs end\cb{itemize}", [text.parbox(w)]) -\end{progcode} - -Occasionally, one would like to have the box in which the text appears to be as -small as possible. Then the \texttt{fancybox} package developed by Timothy Van -Zandt is useful which provides several environments like \texttt{Bitemize} and -\texttt{Beqnarray} which can be processed in LR mode. The relevant part of the -code could look like: -\begin{progcode} -text.set(mode="latex")\\ -text.preamble(r"\cs usepackage\cb{fancybox}")\\ -c = canvas.canvas()\\ -c.text(0, 0, r"\cs begin\cb{Bitemize}\cs item a\cs item b\cs end\cb{Bitemize}") -\end{progcode} -Other environments provided by the \texttt{fancybox} package include -\texttt{Bcenter}, \texttt{Bflushleft}, \texttt{Bflushright}, -\texttt{Benumerate}, and \texttt{Bdescription}. For more details, the -documentation of the package should be consulted. -} - -\question{a}{Font shape `OT1/xyz/m/n' undefined} -{} -{\label{q:fontshape_undefined} -You have asked to use font \texttt{xyz} which is not available. Make sure that -you have this font available in Type1 format, i.e.\ there should be a -file \texttt{xyz.pfb} somewhere. If your \TeX{} system is TDS compliant -(TDS=\TeX{} directory structure, cf.\ \ctan{tds/draft-standard/tds/tds.pdf}) -you should take a look at the subdirectories of -\path{TEXMF/fonts/type1}. -} - -\question{a}{File \dots\ is not available or not readable} -{} -{\label{q:no_lfs} -Such an error message might already occur when running the example file -\texttt{hello.py} included in the \PyX{} distribution. Usually, the error -occurs due to an overly restrictive umask setting applied when unpacking the -\texttt{tar.gz} sources. This may render the file mentioned in the error -message unreadable because the python distutil installation package doesn't -change the file permissions back to readable for everyone. - -If the file exists, the problem can be solved by changing the permissions to -allow read access.} - -\question{a}{No information for font `cmr10' found in font mapping -file} -{} -{\label{q:no_cmr10} -Such an error message can already be encountered by simply running the example -file \texttt{hello.py} included in the \PyX{} distribution. The likely reason -is that the \TeX{} system does not find the cmr fonts in Type1 format. -\PyX{} depends on these fonts as it does not work with the traditional -pk fonts which are stored as bitmaps. - -Therefore, the first thing to make sure is that the cmr Type1 fonts are -installed. In some \TeX{} installations, the command \texttt{kpsewhich -cmr10.pfb} will return the appropriate path if the cmr fonts exist in the -binary Type1 format (extension \texttt{pfb}) required by \PyX. If the command -does not work but the TeX{} system is TDS compliant -(\uaref{q:fontshape_undefined}), a look should be taken at -\path{TEXMF/fonts/type1/bluesky/cm} where \texttt{TEXMF} is the root of the -\texttt{texmf} tree. - -If the Type1 fonts do not exist on the system, they may be obtained from -the CTAN \uaref{q:ctan} at \ctan{fonts/cm/ps-type1/bluesky}. See the -\texttt{README} for information about who produced these fonts and why they -are freely available. - -If the Type1 fonts exist, the next step is to take a look at -\texttt{psfonts.map}. There may be several files with this name on the system, -so it is important to find out which one TeX is actually using. -\texttt{kpsewhich psfonts.map} might give this information. - -The most likely problem is that this file does not contain a line telling TeX -what to do if it encounters a request for font \texttt{cmr10}, i.e. the -following line -may be missing -\begin{progcode} -~~~cmr10~~~~~~~~~~~CMR10~~~~~~~~~~~`_ you may be able to find some useful +information. There exists for example “A Gentle Introduction to TeX” by M. Doob +(`CTAN:gentle/gentle.pdf `_) +and “The Not So Short Introduction to LaTeX2e” +(`CTAN:info/lshort/english/lshort.pdf +`_) by T. Oetiker +et al. The latter has been translated into a variety of languages among them +korean (which you will not be able to read unless you have appropriate fonts +installed) and mongolian. + +Of course, it is likely that these documents will go way beyond what you will +need for generating graphics with PyX so you don't have to read all of it +(unless you want to use TeX or LaTeX for typesetting which can be highly +recommended). + +There exists also a number of FAQs on TeX at `CTAN:help `_. + +.. _ctan: + +What is CTAN? +------------- + +CTAN is the *Comprehensive TeX Archive Network* where you will find almost +everything related to TeX and friends. The main CTAN server is `www.ctan.org +`_ but there exists a large number of mirrors around the +world. You can help to reduce the load on the main server by using +`mirror.ctan.org `_ which will redirect you to a mirror +nearby. A list of known mirrors is available at +`http://mirror.ctan.org/README.mirrors +`_. + +In this FAQ, ``CTAN:`` refers to the root of the CTAN tree, e.g. +`http://www.ctan.org/tex-archive/ `_. The +links to CTAN in this document point to the main server but you might consider +using a server closer to you in order to reduce traffic load. + +Is there support for ConTeXt? +----------------------------- + +No, and as far as I know there no plans to provide it in the near future. +Given the close ties between ConTeXt and MetaPost, ConTeXt users probably +prefer to stick with the latter anyway. + +TeX and LaTeX commands useful for PyX +===================================== + +How do I get a specific symbol with TeX or LaTeX? +------------------------------------------------- + +A list of mathematical symbols together with the appropriate command name can +be found at `CTAN:info/symbols/math/symbols.pdf +`_. A +comprehensive list containing almost 6000 symbols for use with LaTeX can be +obtained from `CTAN:info/symbols/comprehensive/symbols-a4.pdf +`_. +In some cases it might be necessary to install fonts or packages available from +CTAN (cf. :ref:`ctan`). + +TeX and LaTeX errors +==================== + +.. _undefined_usepackage: + +Undefined control sequence ``\usepackage`` +------------------------------------------ + +The command ``\usepackage`` is specific to LaTeX. Since by default PyX +uses TeX, you have to specify the correct mode:: + + text.set(mode="latex") + +Undefined control sequence ``\frac`` + +The command ``\frac`` is only available in LaTeX. The equivalent to +``\frac{a}{b}`` in TeX is ``{a \over b}``. As an alternative you may ask for +the LaTeX mode as explained in :ref:`undefined_usepackage`. + +Missing ``$`` inserted +---------------------- + +You have specified TeX- or LaTeX-code which is only valid in math mode. +Typical examples are greek symbols, sub- and superscripts or fractions. + +On the PyX level, you can specify math mode for the whole string by using +``text.mathmode`` as in :: + + c.text(0, 0, r"{\alpha}", text.mathmode) + +Keep also in mind that the standard Python interpretation of the backslash as +introducing escape sequences needs to be prevented +:ref:`raw_string`. + +On the TeX/LaTeX level you should enclose the commands requiring math mode in +``$``'s. As an example, ``$\alpha_i^j$`` will produce a greek letter alpha with +a subscript i and a superscript j. The dollar sign thus allows you to specify +math mode also for substrings. There exist other ways to specify math mode in +TeX and LaTeX which are particularly useful for more complex typesetting tasks. +To learn more about it, you should consult the documentation +:ref:`intro_tex_latex`. + +Why do environments like ``itemize`` or ``eqnarray`` seem not to work? +---------------------------------------------------------------------- + +An itemize environment might result in a LaTeX error complaining about a +``missing \item`` or an eqnarray might lead to a LaTeX message ``missing +\endgroup inserted`` even though the syntax appears to be correct. The TeXnical +reason is that in PyX text is typeset in left-right mode (LR mode) which does +not allow linebreaks to occur. There are two ways out. + +If the text material should go in a box of given width, a parbox can be used +like in the following example:: + + text.set(mode="latex") + c = canvas.canvas() + w = 2 + c.text(0, 0, r"\begin{itemize}\item a\item b\end{itemize}", [text.parbox(w)]) + +Occasionally, one would like to have the box in which the text appears to be as +small as possible. Then the ``fancybox`` package developed by Timothy Van Zandt +is useful which provides several environments like ``Bitemize`` and +``Beqnarray`` which can be processed in LR mode. The relevant part of the code +could look like:: + + text.set(mode="latex") + text.preamble(r"\usepackage{fancybox}") + c = canvas.canvas() + c.text(0, 0, r"\begin{Bitemize}\item a\item b\end{Bitemize}") + +Other environments provided by the ``fancybox`` package include ``Bcenter``, +``Bflushleft``, ``Bflushright``, ``Benumerate``, and ``Bdescription``. For more +details, the documentation of the package should be consulted. + +.. _fontshape_undefined: + +Font shape ``OT1/xyz/m/n`` undefined +------------------------------------ + +You have asked to use font ``xyz`` which is not available. Make sure that you +have this font available in Type1 format, i.e. there should be a file +``xyz.pfb`` somewhere. If your TeX system is TDS compliant (TDS=TeX directory +structure, cf. `CTAN:tds/draft-standard/tds/tds.pdf +`_) you should +take a look at the subdirectories of ``$TEXMF/fonts/type1``. + +File ``…`` is not available or not readable +------------------------------------------- + +Such an error message might already occur when running the example file +``hello.py`` included in the PyX distribution. Usually, the error occurs due to +an overly restrictive umask setting applied when unpacking the ``tar.gz`` +sources. This may render the file mentioned in the error message unreadable +because the python distutil installation package doesn't change the file +permissions back to readable for everyone. + +If the file exists, the problem can be solved by changing the permissions to +allow read access. + +No information for font ``cmr10`` found in font mapping file +------------------------------------------------------------ + +Such an error message can already be encountered by simply running the example +file ``hello.py`` included in the PyX distribution. The likely reason is that +the TeX system does not find the cmr fonts in Type1 format. PyX depends on +these fonts as it does not work with the traditional pk fonts which are stored +as bitmaps. + +Therefore, the first thing to make sure is that the cmr Type1 fonts are +installed. In some TeX installations, the command ``kpsewhich cmr10.pfb`` will +return the appropriate path if the cmr fonts exist in the binary Type1 format +(extension ``pfb``) required by PyX. If the command does not work but the TeX +system is TDS compliant (:ref:`fontshape_undefined`), a look should be taken at +``$TEXMF/fonts/type1/bluesky/cm`` where ``$TEXMF`` is the root of the ``texmf`` +tree. + +If the Type1 fonts do not exist on the system, they may be obtained from the +CTAN (cf. :ref:`ctan`) at `CTAN:fonts/cm/ps-type1/bluesky +`_). See the +``README`` for information about who produced these fonts and why they are +freely available. + +If the Type1 fonts exist, the next step is to take a look at ``psfonts.map``. +There may be several files with this name on the system, so it is important to +find out which one TeX is actually using. ``kpsewhich psfonts.map`` might give +this information. + +The most likely problem is that this file does not contain a line telling TeX +what to do if it encounters a request for font ``cmr10``, i.e. the following +line may be missing :: + + cmr10 CMR10 `_ and its subdirectories as well as at +the web page `http://home.vr-web.de/was/fonts.html +`_ of Walter Schmidt. It is not unlikely +that somebody has already done most of the work for you and created the files +needed for the font to work properly with LaTeX. But remember: we are talking +about commercial fonts here, so do not expect to find the fonts themselves for +free. + +If none of these cases applies, you should spend some time reading manuals +about font installation, e.g. `CTAN:macros/latex/doc/fntguide.pdf +`_ (of course, I +do not expect font wizards to read the last few lines). + +Can I use a TrueType font with PyX? +----------------------------------- + +Not directly as PyX only knows how to handle Type1 fonts (although it is +possible to get LaTeX to work with TrueType fonts). However, you may use +``ttf2pt1`` (from `ttf2pt1.sourceforge.net `_) +to convert a TrueType font into a Type1 font which you then install in your TeX +system (cf. :ref:`other_font`). You will loose hinting information in the +conversion process but this should not really matter on output devices with not +too low resolution. diff -Nru pyx-0.11.1/faq/tipa.py pyx-0.12.1/faq/tipa.py --- pyx-0.11.1/faq/tipa.py 2011-05-18 09:09:37.000000000 +0000 +++ pyx-0.12.1/faq/tipa.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,9 +0,0 @@ -import sys -sys.path.insert(0, "..") -from pyx import * - -text.set(mode="latex", docclass="scrartcl", docopt="11pt", fontmaps="tipa.map") -text.preamble(r"\usepackage{tipa}") -c = canvas.canvas() -c.text(0, 0, r"\textipa{[%s]}" % sys.argv[1]) -c.writePDFfile("tipa%s.pdf" % sys.argv[2], bboxenlarge=0) Binary files /tmp/tI9GQCH3bv/pyx-0.11.1/faq/tipapych.pdf and /tmp/QyDekqgLs6/pyx-0.12.1/faq/tipapych.pdf differ Binary files /tmp/tI9GQCH3bv/pyx-0.11.1/faq/tipapyks.pdf and /tmp/QyDekqgLs6/pyx-0.12.1/faq/tipapyks.pdf differ Binary files /tmp/tI9GQCH3bv/pyx-0.11.1/faq/tipapyx.pdf and /tmp/QyDekqgLs6/pyx-0.12.1/faq/tipapyx.pdf differ diff -Nru pyx-0.11.1/gallery/graphs/mandel.py pyx-0.12.1/gallery/graphs/mandel.py --- pyx-0.11.1/gallery/graphs/mandel.py 2011-05-18 09:09:36.000000000 +0000 +++ pyx-0.12.1/gallery/graphs/mandel.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,42 +0,0 @@ -# contributed by Stephen Phillips - -from pyx import * - -# Mandelbrot parameters -re_min = -2 -re_max = 0.5 -im_min = -1.25 -im_max = 1.25 -gridx = 100 -gridy = 100 -max_iter = 10 - -# Set-up -re_step = (re_max - re_min) / gridx -im_step = (im_max - im_min) / gridy -d = [] - -# Compute fractal -for re_index in range(gridx): - re = re_min + re_step * (re_index + 0.5) - for im_index in range(gridy): - im = im_min + im_step * (im_index + 0.5) - c = complex(re, im) - n = 0 - z = complex(0, 0) - while n < max_iter and abs(z) < 2: - z = (z * z) + c - n += 1 - d.append([re - 0.5 * re_step, re + 0.5 * re_step, - im - 0.5 * im_step, im + 0.5 * im_step, - float(n)/max_iter]) - -# Plot graph -g = graph.graphxy(height=8, width=8, - x=graph.axis.linear(min=re_min, max=re_max, title=r'$\Re(c)$'), - y=graph.axis.linear(min=im_min, max=im_max, title=r'$\Im(c)$')) -g.plot(graph.data.points(d, xmin=1, xmax=2, ymin=3, ymax=4, color=5), - [graph.style.rect(color.gradient.Rainbow)]) -g.dodata() # plot data first, then axes -g.writeEPSfile('mandel') -g.writePDFfile('mandel') diff -Nru pyx-0.11.1/gallery/misc/pyxpyx.py pyx-0.12.1/gallery/misc/pyxpyx.py --- pyx-0.11.1/gallery/misc/pyxpyx.py 1970-01-01 00:00:00.000000000 +0000 +++ pyx-0.12.1/gallery/misc/pyxpyx.py 2012-10-16 20:56:07.000000000 +0000 @@ -0,0 +1,12 @@ +from pyx import * + +textpath = text.text(0, 0, r"\PyX").textpath().reversed() +decotext = r"\PyX{} is fun! "*50 +scale = text.text(0, 0, decotext).width/textpath.arclen() + +c = canvas.canvas() +c.draw(textpath, [trafo.scale(scale), + deco.filled([color.gray(0.5)]), + deco.curvedtext(decotext)]) +c.writeEPSfile("pyxpyx") +c.writePDFfile("pyxpyx") diff -Nru pyx-0.11.1/gallery/path/tree.py pyx-0.12.1/gallery/path/tree.py --- pyx-0.11.1/gallery/path/tree.py 2011-05-18 09:09:36.000000000 +0000 +++ pyx-0.12.1/gallery/path/tree.py 2012-10-16 21:13:30.000000000 +0000 @@ -1,6 +1,6 @@ -# -*- coding: ISO-8859-1 -*- +# -*- coding: utf-8 -*- # fractal tree -# contributed by Gerhard Schmid and Andr Wobst +# contributed by Gerhard Schmid and André Wobst from pyx import * diff -Nru pyx-0.11.1/manual/Makefile pyx-0.12.1/manual/Makefile --- pyx-0.11.1/manual/Makefile 2011-05-20 13:01:14.000000000 +0000 +++ pyx-0.12.1/manual/Makefile 2012-10-16 20:56:09.000000000 +0000 @@ -20,7 +20,7 @@ @echo "Done." clean: - -rm -rf $(BUILDDIR)/* *.eps *.pdf *.png + -rm -rf $(BUILDDIR)/* *.pdf *.png html: figs $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @@ -36,4 +36,4 @@ %.png: %.py export PYTHONPATH=$(CURDIR)/.. ; $(PYTHON) $^ - $(PYTHON) ../contrib/epstopng.py -o $@ $*.eps + $(PYTHON) ../contrib/epstopng.py -o $@ $*.pdf diff -Nru pyx-0.11.1/manual/arrows.py pyx-0.12.1/manual/arrows.py --- pyx-0.11.1/manual/arrows.py 2011-05-18 09:09:39.000000000 +0000 +++ pyx-0.12.1/manual/arrows.py 2012-10-16 20:56:09.000000000 +0000 @@ -47,5 +47,4 @@ drawdeco("earrow.Large([style.linejoin.round])") drawdeco("earrow.Large([deco.stroked.clear])") -c.writeEPSfile("arrows") -c.writePDFfile("arrows") +c.writePDFfile() diff -Nru pyx-0.11.1/manual/bbox.rst pyx-0.12.1/manual/bbox.rst --- pyx-0.11.1/manual/bbox.rst 2011-05-20 13:01:14.000000000 +0000 +++ pyx-0.12.1/manual/bbox.rst 2012-10-16 20:56:09.000000000 +0000 @@ -1,3 +1,6 @@ + +.. _bbox_module: + .. module:: bbox ****************** @@ -10,8 +13,8 @@ ``bbox())`` method, but you may also construct a bounding box by yourself. -bbox constructor -================ +:class:`bbox` constructor +========================= The ``bbox`` constructor accepts the following keyword arguments @@ -36,8 +39,8 @@ +---------+-----------------------------------------------+ -bbox methods -============ +:class:`bbox` methods +===================== +-------------------------------------------+-----------------------------------------------+ | ``bbox`` method | function | diff -Nru pyx-0.11.1/manual/bitmap.py pyx-0.12.1/manual/bitmap.py --- pyx-0.11.1/manual/bitmap.py 2011-05-18 09:09:39.000000000 +0000 +++ pyx-0.12.1/manual/bitmap.py 2012-10-16 20:56:09.000000000 +0000 @@ -10,6 +10,5 @@ c = canvas.canvas() c.insert(bitmap_bw) c.insert(bitmap_rgb) -c.writeEPSfile("bitmap") -c.writePDFfile("bitmap") +c.writePDFfile() diff -Nru pyx-0.11.1/manual/boxalign.py pyx-0.12.1/manual/boxalign.py --- pyx-0.11.1/manual/boxalign.py 2011-05-18 09:09:39.000000000 +0000 +++ pyx-0.12.1/manual/boxalign.py 2012-10-16 20:56:09.000000000 +0000 @@ -16,6 +16,5 @@ c.stroke(t2.path(centerradius=0.05), [style.linewidth.THin]) c.stroke(path.line(5, 0, 5 + x, y), [deco.earrow.normal]) c.insert(t2) -c.writeEPSfile("boxalign") -c.writePDFfile("boxalign") +c.writePDFfile() diff -Nru pyx-0.11.1/manual/canvas.rst pyx-0.12.1/manual/canvas.rst --- pyx-0.11.1/manual/canvas.rst 2011-05-20 13:01:14.000000000 +0000 +++ pyx-0.12.1/manual/canvas.rst 2012-10-17 14:47:14.000000000 +0000 @@ -8,22 +8,17 @@ .. sectionauthor:: Jörg Lehmann -One of the central modules for the PostScript access in PyX is named ``canvas``. -Besides providing the class ``canvas``, which presents a collection of visual -elements like paths, other canvases, TeX or LaTeX elements, it contains the -class ``canvas.clip`` which allows clipping of the output. +In addition it +contains the class ``canvas.clip`` which allows clipping of the output. -A canvas may also be embedded in another one using its ``insert`` method. This -may be useful when you want to apply a transformation on a whole set of -operations.. +Class :class:`canvas` +--------------------- -Class canvas ------------- - -This is the basic class of the canvas module, which serves to collect various -graphical and text elements you want to write eventually to an (E)PS file. - +This is the basic class of the canvas module. Instances of this class collect +visual elements like paths, other canvases, TeX or LaTeX elements. A canvas may +also be embedded in another one using its ``insert`` method. This may be useful +when you want to apply a transformation on a whole set of operations. .. class:: canvas(attrs=[], texrunner=None) @@ -38,7 +33,10 @@ .. method:: canvas.draw(path, attrs) - Draws *path* on the canvas applying the given *attrs*. + Draws *path* on the canvas applying the given *attrs*. Depending on the + *attrs* the path will be filled, stroked, ornamented, or a combination + thereof. For the common first two cases the following two convenience + functions are provided. .. method:: canvas.fill(path, attrs=[]) @@ -57,9 +55,9 @@ .. method:: canvas.insert(item, attrs=[]) Inserts an instance of :class:`base.canvasitem` into the canvas. If *attrs* are - present, *item* is inserted into a new :class:`canvas`\ instance with *attrs* as - arguments passed to its constructor is created. Then this :class:`canvas` - instance is inserted itself into the canvas. + present, *item* is inserted into a new :class:`canvas` instance with *attrs* + as arguments passed to its constructor. Then this :class:`canvas` instance + is inserted itself into the canvas. Text output on the canvas is possible using @@ -67,7 +65,22 @@ .. method:: canvas.text(x, y, text, attrs=[]) Inserts *text* at position (*x*, *y*) into the canvas applying *attrs*. This is - a shortcut for ``insert(texrunner.text(x, y, text, attrs))``). + a shortcut for ``insert(texrunner.text(x, y, text, attrs))``. + +To group drawing operations, layers can be used: + + +.. method:: canvas.layer( name, above=None, below=None) + + This method creates or gets a layer with name *name*. + + A layer is a canvas itself and can be used to combine drawing operations for + ordering purposes, i.e., what is above and below each other. The layer name + *name* is a dotted string, where dots are used to form a hierarchy of layer + groups. When inserting a layer, it is put on top of its layer group except + when another layer of this group is specified by means of the parameters + *above* or *below*. + The :class:`canvas` class provides access to the total geometrical size of its element: @@ -75,17 +88,17 @@ .. method:: canvas.bbox() - Returns the bounding box enclosing all elements of the canvas. + Returns the bounding box enclosing all elements of the canvas (see Sect. :mod:`bbox`). -A canvas also allows one to set its TeX runner: +A canvas also allows to set its TeX runner: .. method:: canvas.settexrunner(texrunner) Sets a new *texrunner* for the canvas. -The contents of the canvas can be written using the following two convenience -methods, which wrap the canvas into a single page document. +The contents of the canvas can be written to a file using the following +convenience methods, which wrap the canvas into a single page document. .. method:: canvas.writeEPSfile(file, *args, **kwargs) @@ -94,7 +107,7 @@ write method or it is used as a string containing the filename (the extension ``.eps`` is appended automatically, if it is not present). This method constructs a single page document, passing *args* and *kwargs* to the - :class:`document.page` constructor and the calls the :meth:`writeEPSfile` method + :class:`document.page` constructor and calls the :meth:`writeEPSfile` method of this :class:`document.document` instance passing the *file*. @@ -115,35 +128,53 @@ *kwargs*. -.. method:: canvas.pipeGS(filename="-", device=None, resolution=100, gscommand="gs", gsoptions="", textalphabits=4, graphicsalphabits=4, ciecolor=False, input="eps", **kwargs) +.. method:: canvas.pipeGS(device, seekable=False, resolution=100, gscommand="gs", gsoptions="", textalphabits=4, graphicsalphabits=4, ciecolor=False, input="eps", **kwargs) This method pipes the content of a canvas to the ghostscript interpreter - directly to generate other output formats. At least *filename* or *device* must - be set. *filename* specifies the name of the output file. No file extension will - be added to that name in any case. When no *filename* is specified, the output - is written to stdout. *device* specifies a ghostscript output device by a - string. Depending on your ghostscript configuration ``"png16"``, ``"png16m"``, - ``"png256"``, ``"png48"``, ``"pngalpha"``, ``"pnggray"``, ``"pngmono"``, - ``"jpeg"``, and ``"jpeggray"`` might be available among others. See the output - of ``gs --help`` and the ghostscript documentation for more information. When - *filename* is specified but the device is not set, ``"png16m"`` is used when the - filename ends in ``.png`` and ``"jpeg"`` is used when the filename ends in - ``.jpg``. + to generate other output formats. The output is returned by means of a + python file object. As this file object is generated from a pipe, it is + not seekable by default. To make it seekable, enable the *seekable* flag. + *device* specifies a ghostscript output device by a string. Depending on the + ghostscript configuration ``"png16"``, ``"png16m"``, ``"png256"``, + ``"png48"``, ``"pngalpha"``, ``"pnggray"``, ``"pngmono"``, ``"jpeg"``, and + ``"jpeggray"`` might be available among others. See the output of ``gs + --help`` and the ghostscript documentation for more information. *resolution* specifies the resolution in dpi (dots per inch). *gscmd* is the - command to be used to invoke ghostscript. *gsoptions* are an option string - passed to the ghostscript interpreter. *textalphabits* are *graphicsalphabits* - are conventient parameters to set the ``TextAlphaBits`` and - ``GraphicsAlphaBits`` options of ghostscript. You can skip the addition of those - option by set their value to ``None``. *ciecolor* adds the ``-dUseCIEColor`` - flag to improve the CMYK to RGB color conversion. *input* can be either - ``"eps"`` or ``"pdf"`` to select the input type to be passed to ghostscript - (note slightly different features available in the different input types). + command to be used to invoke ghostscript. *gsoptions* is an option string + passed to the ghostscript interpreter. *textalphabits* and *graphicsalphabits* + are convenient parameters to set the ``TextAlphaBits`` and + ``GraphicsAlphaBits`` options of ghostscript. The addition of these options + can be skipped by setting their values to ``None``. *ciecolor* adds the + ``-dUseCIEColor`` flag to improve the CMYK to RGB color conversion. *input* + can be either ``"eps"`` or ``"pdf"`` to select the input type to be passed + to ghostscript (note slightly different features available in the different + input types regarding e.g. :mod:`epsfile` inclusion and transparency). *kwargs* are passed to the :meth:`writeEPSfile` method (not counting the *file* parameter), which is used to generate the input for ghostscript. By that you gain access to the :class:`document.page` constructor arguments. +.. method:: canvas.writeGSfile(filename=None, device=None, **kwargs) + + This method is similar to pipeGS, but the content is written into the file + *filename*. If filename is None it is auto-guessed from the script name. If + filename is "-", the output is written to stdout. In both cases, a device + needs to be specified to define the format (and the file suffix in case the + filename is created from the script name). + + If device is None, but a filename with suffix is given, PNG files will + be written using the png16m device and JPG files using the jpeg device. + + All other arguments are identical to those of the :meth:`canvas.pipeGS`. + For more information about the possible arguments of the :class:`document.page` constructor, we refer to Sect. :mod:`document`. + +Class :class:`clip` +--------------------- + +In addition the canvas module contains the class ``canvas.clip`` which allows for +clipping of the output by passing a clipping instance to the attrs parameter of +the canvas constructor. diff -Nru pyx-0.11.1/manual/color.py pyx-0.12.1/manual/color.py --- pyx-0.11.1/manual/color.py 2011-05-18 09:09:39.000000000 +0000 +++ pyx-0.12.1/manual/color.py 2012-10-16 20:56:09.000000000 +0000 @@ -8,6 +8,5 @@ c.fill(path.rect(1, 1, 1, 1), [color.rgb.red]) c.fill(path.rect(3, 1, 1, 1), [color.rgb.green]) c.fill(path.rect(5, 1, 1, 1), [color.rgb.blue]) -c.writeEPSfile("color") -c.writePDFfile("color") +c.writePDFfile() diff -Nru pyx-0.11.1/manual/color.rst pyx-0.12.1/manual/color.rst --- pyx-0.11.1/manual/color.rst 2011-05-20 13:01:14.000000000 +0000 +++ pyx-0.12.1/manual/color.rst 2012-10-16 21:13:30.000000000 +0000 @@ -53,8 +53,14 @@ Color gradients =============== -The color module provides a class ``gradient`` for continous transitions between -colors. A list of named gradients is available in appendix :ref:`gradientname`. +The color module provides a class :class:`gradient` for continous transitions between +colors. A list of named gradients is available in appendix :ref:`gradientname`. + +Note that all predefined non-gray gradients are defined in the RGB color space, +except for `gradient.Rainbow`, `gradient.ReverseRainbow`, `gradient.Hue`, and +`gradient.ReverseHue`, which are naturally defined in the HSB color space. Converted +RGB and CMYK versions of these latter gradients are also defined under the names +`rgbgradient.Rainbow` and `cmykgradient.Rainbow`, etc. .. class:: gradient(min=0, max=1) @@ -97,6 +103,19 @@ E.g. for ``type="rgb"`` this dictionary must have the keys ``"r"``, ``"g"``, and ``"b"``. +.. class:: class rgbgradient(gradient) + + This class takes an arbitrary gradient and converts it into one in the RGB color model. + This is useful for instance in bitmap output, where only certain color models + are supported in Postscript/PDF. + +.. class:: class cmykgradient(gradient) + + This class takes an arbitrary gradient and converts it into one in the CMYK color mode. + This is useful for instance in bitmap output, where only certain color models + are supported in Postscript/PDF. + + Transparency ============ diff -Nru pyx-0.11.1/manual/colorname.py pyx-0.12.1/manual/colorname.py --- pyx-0.11.1/manual/colorname.py 2011-05-18 09:09:39.000000000 +0000 +++ pyx-0.12.1/manual/colorname.py 2012-10-16 20:56:09.000000000 +0000 @@ -42,5 +42,4 @@ y = 0 x += dx -c.writeEPSfile("colorname") -c.writePDFfile("colorname") +c.writePDFfile() diff -Nru pyx-0.11.1/manual/conf.py pyx-0.12.1/manual/conf.py --- pyx-0.11.1/manual/conf.py 2011-05-20 13:01:14.000000000 +0000 +++ pyx-0.12.1/manual/conf.py 2012-10-16 08:58:05.000000000 +0000 @@ -27,7 +27,7 @@ # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['mathjax'] +extensions = ['sphinx.ext.mathjax', 'sphinx.ext.todo'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -105,7 +105,7 @@ # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +html_title = 'PyX %s Manual' % release # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None @@ -168,11 +168,13 @@ # Output file base name for HTML help builder. htmlhelp_basename = 'PyXdoc' +todo_include_todos = True + # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' +latex_paper_size = 'a4' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' @@ -180,7 +182,7 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('manual', 'PyX.tex', u'PyX Documentation', + ('manual', 'manual.tex', u'PyX Manual', u'Jörg Lehmann, Michael Schindler, André Wobst', 'manual'), ] @@ -199,7 +201,12 @@ #latex_show_urls = False # Additional stuff for the LaTeX preamble. -#latex_preamble = '' +latex_preamble = r''' + \hypersetup{pdftitle={PyX Manual}, + pdfauthor={Joerg Lehmann , Michael Schindler , Andre Wobst }, + pdfsubject={PyX Manual}, + pdfkeywords={PyX, graphics, manual}} +''' # Documents to append as an appendix to all manuals. #latex_appendices = [] @@ -213,10 +220,11 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('manual', 'pyx', u'PyX Documentation', + ('manual', 'pyx', u'PyX Manual', [u'Jörg Lehmann, Michael Schindler, André Wobst'], 1) ] # -- Other options ------------------------------------------------------------ -mathjax_path = 'http://mathjax.connectmv.com/MathJax.js' +mathjax_path = 'http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=default' +todo_include_todos = True diff -Nru pyx-0.11.1/manual/connector.py pyx-0.12.1/manual/connector.py --- pyx-0.11.1/manual/connector.py 2011-05-18 09:09:39.000000000 +0000 +++ pyx-0.12.1/manual/connector.py 2012-10-16 20:56:09.000000000 +0000 @@ -119,7 +119,6 @@ # write everything c1.insert(c2, [trafo.translate(6.5, 0)]) -c1.writeEPSfile("connector", paperformat=document.paperformat.A4) -c1.writePDFfile("connector") +c1.writePDFfile() # vim:foldmethod=marker:foldmarker=<<<,>>> diff -Nru pyx-0.11.1/manual/deformer.rst pyx-0.12.1/manual/deformer.rst --- pyx-0.11.1/manual/deformer.rst 2011-05-20 13:01:14.000000000 +0000 +++ pyx-0.12.1/manual/deformer.rst 2012-10-16 20:56:09.000000000 +0000 @@ -7,9 +7,8 @@ The :mod:`deformer` module provides techniques to generate modulated paths. All classes in the :mod:`deformer` module can be used as attributes when -drawing/stroking paths onto a canvas, but also independently for manipulating -previously created paths. The difference to the classes in the :mod:`deco` -module is that here, a totally new path is constructed. +drawing/stroking paths onto a canvas. Alternatively new paths can be created by +deforming an existing path by means of the :meth:`deform` method. All classes of the :mod:`deformer` module provide the following methods: @@ -43,22 +42,23 @@ *curvesperhloop*: the number of Bezier curves to approximate a half-loop - *sign*: with ``sign>=0`` starts the cycloid to the left of the path, ``sign<0`` - to the right. + *sign*: for ``sign>=0`` the cycloid starts to the left of the path, whereas + for ``sign<0`` it starts to the right. - *turnangle*: the angle of perspective on the 3D spring. At ``turnangle=0`` one - sees a sinusoidal curve, at ``turnangle=90`` one essentially sees a circle. + *turnangle*: the angle of perspective on the 3D spring. At ``turnangle=0`` + results in a sinusoidal curve, whereas for ``turnangle=90`` one essentially + obtains a circle. .. class:: smoothed(radius, softness=1, obeycurv=0, relskipthres=0.01) This deformer creates a smoothed variant of the original path. The smoothing is - done on the basis of the corners of the original path, not on a global skope! + done on the basis of the corners of the original path, not on a global scope! Therefore, the result might not be what one would draw by hand. At each corner - (or wherever two path elements meet) a piece of length :math:`2\times` *radius* + (or wherever two path elements meet) a piece of twice the *radius* is taken out of the original path and replaced by a curve. This curve is determined by the tangent directions and the curvatures at its endpoints. Both - are given from the original path, and therefore, the new curve fits into the gap + are taken from the original path, and therefore, the new curve fits into the gap in a *geometrically smooth* way. Path elements that are shorter than *radius* :math:`\times` *relskipthres* are ignored. @@ -98,28 +98,29 @@ points with infinte curvature. The resulting path stops at such points and leaves the too strongly curved piece out. - * When the original path contains self-intersection, then the resulting parallel - path is not continuous in the parameterisation of the original path. It may - first take a piece that corresponds to "later" parameter values and then - continue with an "earlier" one. Please don't get confused. + * When the original path contains on or more self-intersections, then the + resulting parallel path is not continuous in the parameterisation of the + original path. This may result in the surprising behaviour that a piece + that corresponding to a "later" parameter value is followed by an + "earlier" one. The parameters are the following: *distance* is the minimal (signed) distance between the original and the parallel paths. - *relerr* is the allowed error in the distance is given by ``distance*relerr``. + *relerr* is the allowed relative error in the distance. - *sharpoutercorners* connects the parallel pathitems by wegde build of straight - lines, instead of taking circular arcs. This preserves the angle of the original - corners. + *sharpoutercorners* connects the parallel pathitems by a wegde made of + straight lines, instead of taking circular arcs. This preserves the angle of + the original corners. *dointersection* is a boolean for performing the last step, the intersection step, in the path construction. Setting this to 0 gives the full parallel path, which can be favourable for self-intersecting paths. *checkdistanceparams* is a list of parameter values in the interval (0,1) where - the distance is checked on each parallel pathitem + the distance is checked on each parallel pathitem. *lookforcurvatures* is the number of points per normpathitem where its curvature - is checked for critical values + is checked for critical values. diff -Nru pyx-0.11.1/manual/document.rst pyx-0.12.1/manual/document.rst --- pyx-0.11.1/manual/document.rst 2011-05-20 13:01:14.000000000 +0000 +++ pyx-0.12.1/manual/document.rst 2012-10-16 21:13:30.000000000 +0000 @@ -44,29 +44,31 @@ .. method:: document.writeEPSfile(file, title=None, strip_fonts=True, text_as_path=False, mesh_as_bitmap=False, mesh_as_bitmap_resolution=300) - Write a single page :class:`document` to an EPS file. *title* is used as the - document title, *strip_fonts* enabled font stripping (removal of unused glyphs), - *text_as_path* converts all text to paths instead of using fonts in the output, - *mesh_as_bitmap* converts meshs (like 3d surface plots) to bitmaps (to reduce - complexity in the output) and *mesh_as_bitmap_resolution* is the resolution of - this conversion in dots per inch. + Write a single page :class:`document` to an EPS file or to stdout if *file* is + set to *-*. *title* is used as the document title, *strip_fonts* enabled + font stripping (removal of unused glyphs), *text_as_path* converts all text + to paths instead of using fonts in the output, *mesh_as_bitmap* converts + meshs (like 3d surface plots) to bitmaps (to reduce complexity in the + output) and *mesh_as_bitmap_resolution* is the resolution of this conversion + in dots per inch. .. method:: document.writePSfile(file, writebbox=False, title=None, strip_fonts=True, text_as_path=False, mesh_as_bitmap=False, mesh_as_bitmap_resolution=300) - Write :class:`document` to a PS file. *writebbox* add the page bounding boxes to - the output. All other parameters are identical to the :meth:`writeEPSfile` - method. + Write :class:`document` to a PS file or to to stdout if *file* is set to + *-*. *writebbox* add the page bounding boxes to the output. All other + parameters are identical to the :meth:`writeEPSfile` method. .. method:: document.writePDFfile(file, title=None, author=None, subject=None, keywords=None, fullscreen=False, writebbox=False, compress=True, compresslevel=6, strip_fonts=True, text_as_path=False, mesh_as_bitmap=False, mesh_as_bitmap_resolution=300) - Write :class:`document` to a PDF file. *author*, *subject*, and *keywords* are - used for the document author, subject, and keyword information, respectively. - *fullscreen* enabled fullscreen mode when the document is opened, *writebbox* - enables writing of the crop box to each page, *compress* enables output stream - compression and *compresslevel* sets the compress level to be used (from 1 to - 9). All other parameters are identical to the :meth:`writeEPSfile`. + Write :class:`document` to a PDF file or to stdout if *file* is set to *-*. + *author*, *subject*, and *keywords* are used for the document author, + subject, and keyword information, respectively. *fullscreen* enabled + fullscreen mode when the document is opened, *writebbox* enables writing of + the crop box to each page, *compress* enables output stream compression and + *compresslevel* sets the compress level to be used (from 1 to 9). All other + parameters are identical to the :meth:`writeEPSfile`. .. method:: document.writetofile(filename, *args, **kwargs) diff -Nru pyx-0.11.1/manual/epsfile.rst pyx-0.12.1/manual/epsfile.rst --- pyx-0.11.1/manual/epsfile.rst 2011-05-20 13:01:14.000000000 +0000 +++ pyx-0.12.1/manual/epsfile.rst 2012-10-16 20:56:09.000000000 +0000 @@ -1,3 +1,4 @@ + .. module:: epsfile ***************************************** diff -Nru pyx-0.11.1/manual/gradientname.py pyx-0.12.1/manual/gradientname.py --- pyx-0.11.1/manual/gradientname.py 2011-05-18 09:09:39.000000000 +0000 +++ pyx-0.12.1/manual/gradientname.py 2012-10-16 09:19:56.000000000 +0000 @@ -1,4 +1,8 @@ #!/usr/bin/env python + +# WARNING: THIS IS REALLY OLD CODE. IT COULD PROBABLY BE DONE USING GRAPHX NOWADAYS. +# HOWEVER, WE DON'T CARE. JUST DON'T TAKE THIS CODE TOO SERIOUSLY. + import sys, imp, re sys.path[:0] = [".."] import pyx @@ -37,7 +41,7 @@ firstgraph = g = graph.graphxy(ypos=y, width=10, height=0.5, x2=xaxis, y=graph.axis.lin(parter=None)) else: g = graph.graphxy(ypos=y, width=10, height=0.5, x2=graph.axis.linkedaxis(firstgraph.axes["x2"]), y=graph.axis.lin(parter=None)) - g.plot(pf, [graph.style.rect(getattr(pyx.color.gradient, m.group("name")))]) + g.plot(pf, [graph.style.rect(gradient=getattr(pyx.color.gradient, m.group("name")), keygraph=None)]) g.doplot() g.finish() c.insert(g) @@ -45,5 +49,4 @@ y += dy -c.writeEPSfile("gradientname") -c.writePDFfile("gradientname") +c.writePDFfile() diff -Nru pyx-0.11.1/manual/gradientname.rst pyx-0.12.1/manual/gradientname.rst --- pyx-0.11.1/manual/gradientname.rst 2011-05-20 13:01:14.000000000 +0000 +++ pyx-0.12.1/manual/gradientname.rst 2012-10-16 20:56:09.000000000 +0000 @@ -1,3 +1,4 @@ + .. _gradientname: ************************* diff -Nru pyx-0.11.1/manual/graph.py pyx-0.12.1/manual/graph.py --- pyx-0.11.1/manual/graph.py 2011-05-18 09:09:39.000000000 +0000 +++ pyx-0.12.1/manual/graph.py 2012-10-16 20:56:09.000000000 +0000 @@ -1,5 +1,4 @@ from pyx import * g = graph.graphxy(width=8) g.plot(graph.data.file("graph.dat", x=1, y=2)) -g.writeEPSfile("graph") -g.writePDFfile("graph") +g.writePDFfile() diff -Nru pyx-0.11.1/manual/graph.rst pyx-0.12.1/manual/graph.rst --- pyx-0.11.1/manual/graph.rst 2011-05-20 13:01:14.000000000 +0000 +++ pyx-0.12.1/manual/graph.rst 2012-10-17 14:44:41.000000000 +0000 @@ -208,11 +208,11 @@ fail. Usually you do not need to take care about the finalization of the graph, because it happens automatically once you write the plot into a file. However, sometimes position methods (described below) are nice to be accessible. For -that, at least the layout of the graph must have been finished. By calling the -:meth:`do`\ -methods yourself you can also alter the order in which the graph -components are plotted. Multiple calls to any of the :meth:`do`\ -methods have -no effect (only the first call counts). The orginal order in which the -:meth:`do`\ -methods are called is: +that, at least the layout of the graph must have been finished. However, the +drawing order is based on canvas layers and thus the order in which the +:meth:`do`\ -methods are called will not alter the output. Multiple calls to +any of the :meth:`do`\ -methods have no effect (only the first call counts). +The orginal order in which the :meth:`do`\ -methods are called is: .. method:: graphxy.dolayout() @@ -709,15 +709,16 @@ providing certain internal data. -.. class:: pos(epsilon=1e-10) +.. class:: pos(usenames={}, epsilon=1e-10) This class is a hidden style providing a position in the graph. It needs a data column for each graph dimension. For that the column names need to be equal to - an axis name. Data points are considered to be out of graph when their position - in graph coordinates exceeds the range [0:1] by more than *epsilon*. + an axis name, or a name translation from axis names to column names need to be + given by *usenames*. Data points are considered to be out of graph when their + position in graph coordinates exceeds the range [0:1] by more than *epsilon*. -.. class:: range(usenames=, epsilon=1e-10) +.. class:: range(usenames={}, epsilon=1e-10) This class is a hidden style providing an errorbar range. It needs data column names constructed out of a axis name ``X`` for each dimension errorbar data @@ -928,12 +929,17 @@ be added to the line. -.. class:: rect(gradient=color.gradient.Grey) +.. class:: rect(colorname="color", gradient=color.gradient.Grey, coloraxis=None, keygraph=_autokeygraph) This class is a style to plot colored rectangles into a two-dimensional graph. The size of the rectangles is taken from the data provided by the :class:`range` - style. The additional data column named ``color`` specifies the color of the - rectangle defined by *gradient*. The valid color range is [0:1]. + style. The additional data column named *colorname* specifies the color of the + rectangle defined by *gradient*. The translation of the data values to the + gradient is done by the *coloraxis*, which is set to be a linear axis if not + provided by *coloraxis*. A key graph, a graphx instance, is generated + automatically to indicate the color scale if not provided by *keygraph*. + If a *keygraph* is given, its ``x`` axis defines the color conversion and + *coloraxis* is ignored. .. class:: histogram(lineattrs=[], steps=0, fromvalue=0, frompathattrs=[], fillable=0, rectkey=0, autohistogramaxisindex=0, autohistogrampointpos=0.5, epsilon=1e-10) @@ -1033,21 +1039,11 @@ ``changelinestyle`` of the :class:`line` class. -.. class:: surface(colorname="color", gradient=color.gradient.Grey, mincolor=None, maxcolor=None, gridlines1=0.05, gridlines2=0.05, gridcolor=None, backcolor=color.gray.black) +.. class:: surface(gridlines1=0.05, gridlines2=0.05, gridcolor=None, backcolor=color.gray.black, **kwargs) Draws a surface of a rectangular grid. Each rectangle is divided into 4 triangles. - The grid can be colored using values provided by the data column named - *colorname*. The values are rescaled to the range [0:1] using mincolor and - maxcolor (which are taken from the minimal and maximal values, but larger bounds - could be set). - - If no *colorname* column exists, the surface style falls back to a lighting - coloring taking into account the angle between the view ray and the triangle and - the distance between viewer and triangle. The precise conversion is defined in - the :meth:`lighting` method. - If a *gridcolor* is set, the rectangular grid is marked by small stripes of the relative (compared to each rectangle) size of *gridlines1* and *gridlines2* for the first and second grid direction, respectively. @@ -1061,8 +1057,9 @@ * All colors must use the same color space. * HSB colors are not allowed, whereas Gray, RGB, and CMYK are allowed. You can - convert HSB colors into a different color space before passing them to the - surface. + convert HSB colors into a different color space by means of + :class:`rgbgradient` and class:`cmykgradient` before passing it to the + surface style. * The grid itself is also constructed out of triangles. The grid is transformed along with the triangles thus looking quite different from a stroked grid (as @@ -1072,6 +1069,22 @@ * Color changes are continuous (in the selected color space) for each triangle. + Further arguments are identical to the :class:`graph.style.rect` style. However, + if no *colorname* column exists, the surface style falls back to a lighting + coloring taking into account the angle between the view ray and the triangle and + the distance between viewer and triangle. The precise conversion is defined in + the :meth:`lighting` method. + + +.. class:: density(epsilon=1e-10, **kwargs): + + Density plots can be created by the density style. It is similar to a surface + plot in 2d, but it does not use a mesh, but a bitmap representation instead. + Due to that difference, the file size is smaller and not color interpolation + takes place. Furthermore the style can be used with equidistantly spaced data + only (after conversion by the axis, so logarithmic raw data and such are + possible using proper axes). Further arguments are identical to the + :class:`graph.style.rect` style. .. module:: graph.key diff -Nru pyx-0.11.1/manual/graph2.py pyx-0.12.1/manual/graph2.py --- pyx-0.11.1/manual/graph2.py 2011-05-18 09:09:39.000000000 +0000 +++ pyx-0.12.1/manual/graph2.py 2012-10-16 20:56:09.000000000 +0000 @@ -2,5 +2,4 @@ g = graph.graphxy(width=8) g.plot(graph.data.file("graph.dat", x=1, y=2)) g.plot(graph.data.function("y(x)=x**2")) -g.writeEPSfile("graph2") -g.writePDFfile("graph2") +g.writePDFfile() diff -Nru pyx-0.11.1/manual/graph3.py pyx-0.12.1/manual/graph3.py --- pyx-0.11.1/manual/graph3.py 2011-05-18 09:09:39.000000000 +0000 +++ pyx-0.12.1/manual/graph3.py 2012-10-16 20:56:09.000000000 +0000 @@ -2,5 +2,4 @@ g = graph.graphxy(width=8, x=graph.axis.linear(min=-5, max=5), y=graph.axis.logarithmic()) g.plot(graph.data.function("y(x)=exp(x)")) -g.writeEPSfile("graph3") -g.writePDFfile("graph3") +g.writePDFfile() diff -Nru pyx-0.11.1/manual/graphics.rst pyx-0.12.1/manual/graphics.rst --- pyx-0.11.1/manual/graphics.rst 2011-05-20 13:01:14.000000000 +0000 +++ pyx-0.12.1/manual/graphics.rst 2012-10-17 13:48:24.000000000 +0000 @@ -1,3 +1,4 @@ + .. _graphics: ************** @@ -16,7 +17,9 @@ curves. Such a path does not have to be connected but may also comprise several disconnected segments, which will be called *subpaths* in the following. -XXX example for paths and subpaths (figure) +.. todo:: + + example for paths and subpaths (figure) Usually, a path is constructed by passing a list of the path primitives :class:`moveto`, :class:`lineto`, :class:`curveto`, etc., to the constructor of @@ -73,7 +76,7 @@ path.moveto(1, 0), path.lineto(0, 0)) which would construct a rectangle out of four disconnected subpaths (see Fig. -:ref:`fig_rects`\ a). In a better solution (see Fig. :ref:`fig_rects`\ b), the +:ref:`fig_rects` a). In a better solution (see Fig. :ref:`fig_rects` b), the pen is not lifted between the first and the last point: .. _fig_rects: @@ -90,7 +93,7 @@ path.lineto(1, 1), path.lineto(1, 0), path.lineto(0, 0)) -However, as one can see in the lower left corner of Fig. :ref:`fig_rects`\ b, +However, as one can see in the lower left corner of Fig. :ref:`fig_rects` b, the rectangle is still incomplete. It needs to be closed, which can be done explicitly by using for the last straight line of the rectangle (from the point :math:`(0, 1)` back to the origin at :math:`(0, 0)`) the :class:`closepath` @@ -103,15 +106,15 @@ The :class:`closepath` directive adds a straight line from the current point to the first point of the current subpath and furthermore *closes* the sub path, i.e., it joins the beginning and the end of the line segment. This results in -the intended rectangle shown in Fig. :ref:`fig_rects`\ c. Note that filling the +the intended rectangle shown in Fig. :ref:`fig_rects` c. Note that filling the path implicitly closes every open subpath, as is shown for a single subpath in -Fig. :ref:`fig_rects`\ d), which results from :: +Fig. :ref:`fig_rects` d), which results from :: - c.stroke(rect2, [deco.filled([color.grey(0.95)])]) + c.stroke(rect2, [deco.filled([color.grey(0.5)])]) Here, we supply as second argument of the :meth:`stroke` method a list which in the present case only consists of a single element, namely the so called -decorator :class:`deco.filled`. As it name says, this decorator specifies that +decorator :class:`deco.filled`. As its name says, this decorator specifies that the path is not only being stroked but also filled with the given color. More information about decorators, styles and other attributes which can be passed as elements of the list can be found in Sect. :ref:`graphics_attributes`. More @@ -149,7 +152,7 @@ .. figure:: radii.* :align: center - Example: Intersection of circle with line yielding two radii. + Example: Intersection of circle with line yielding two radii Here, the basic elements, a circle around the point :math:`(0, 0)` with radius :math:`2` and a straight line, are defined. Then, passing the *line*, to the @@ -183,7 +186,7 @@ .. figure:: radii2.* :align: center - Example: Intersection of circle with line yielding radii and circle segment. + Example: Intersection of circle with line yielding radii and circle segment Here, we first split the circle using the :meth:`split` method passing the list of parameters obtained above. Since the circle is closed, this yields two arc @@ -209,12 +212,10 @@ circle consists just of one :class:`arc` together with a :class:`closepath` element. However, this is not the case: the actual range is much larger. The reason for this behaviour lies in the internal path handling of PyX: Before -performing any non-trivial geometrical operation with a path, it will +performing any non-trivial geometrical operation on a path, it will automatically be converted into an instance of the :class:`normpath` class (see also Sect. :class:`path.normpath`). These so generated paths are already separated in their subpaths and only contain straight lines and Bézier curve segments. -Thus, as is easily imaginable, they are much simpler to deal with. - XXX explain normpathparams and things like p.begin(), p.end()-1, A more geometrical way of accessing a point on the path is to use the arc length @@ -233,12 +234,11 @@ will draw a straight line from a point at angle :math:`180` degrees (in radians :math:`\pi`) to another point at angle :math:`270` degrees (in radians :math:`3\pi/2`) on a circle with radius :math:`r=2`. Note however, that the -mapping arc length :math:`\to` point is in general discontinuous at the begin +mapping from an arc length to a point is in general discontinuous at the beginning and the end of a subpath, and thus PyX does not guarantee any particular result for this boundary case. -More information on the available path methods can be found in Sect. -:class:`path.path`. +More information on the available path methods can be found in Sect. :ref:`postscript_like_paths`. .. _graphics_attributes: @@ -247,10 +247,10 @@ ================================== Attributes define properties of a given object when it is being used. Typically, -there are different kind of attributes which are usually orthogonal to each +there are different kinds of attributes which are usually orthogonal to each other, while for one type of attribute, several choices are possible. An example is the stroking of a path. There, linewidth and linestyle are different kind of -attributes. The linewidth might be normal, thin, thick, etc, and the linestyle +attributes. The linewidth might be thin, normal, thick, etc., and the linestyle might be solid, dashed etc. Attributes always occur in lists passed as an optional keyword argument to a @@ -264,7 +264,7 @@ attributes useful default values are stored as member variables of the actual attribute. For instance, ``style.linewidth.Thick`` is equivalent to ``style.linewidth(0.04, type="w", unit="cm")``, that is :math:`0.04` width cm -(see Sect. :mod:`unit` for more information about PyX's unit system). +(see Sect. :ref:`module_unit` for more information about PyX's unit system). Another important feature of PyX attributes is what is call attributed merging. A trivial example is the following:: @@ -275,7 +275,7 @@ Here, the ``style.linewidth.thin`` attribute overrides the preceding ``style.linewidth.Thick`` declaration. This is especially important in more -complex cases where PyXdefines default attributes for a certain operation. When +complex cases where PyX defines default attributes for a certain operation. When calling the corresponding methods with an attribute list, this list is appended to the list of defaults. This way, the user can easily override certain defaults, while leaving the other default values intact. In addition, every @@ -288,9 +288,9 @@ The clear attribute is also provided by the base classes of the various styles. For instance, :class:`style.strokestyle.clear` clears all strokestyle subclasses -and thus :class:`style.linewidth` and :class:`style.linestyle`. Since all +i.e. :class:`style.linewidth` and :class:`style.linestyle`. Since all attributes derive from :class:`attr.attr`, you can remove all defaults using -``attr.clear``. An overview over the most important attribute typesprovided by +``attr.clear``. An overview over the most important attribute types provided by PyX is given in the following table. +----------------------------+---------------------------------+------------------------------------+ @@ -298,7 +298,8 @@ +============================+=================================+====================================+ | :class:`deco.deco` | decorator specifying the way | :class:`deco.stroked`, | | | the path is drawn | :class:`deco.filled`, | -| | | :class:`deco.arrow` | +| | | :class:`deco.arrow`, | +| | | :class:`deco.text` | +----------------------------+---------------------------------+------------------------------------+ | :class:`style.strokestyle` | style used for path stroking | :class:`style.linecap`, | | | | :class:`style.linejoin`, | @@ -311,9 +312,9 @@ | :class:`style.fillstyle` | style used for path filling | :class:`color.color`, | | | | :class:`pattern.pattern` | +----------------------------+---------------------------------+------------------------------------+ -| :class:`style.filltype` | type of path filling | ``style.filltype.nonzero_winding`` | +| :class:`style.filltype` | type of path filling | ``style.fillrule.nonzero_winding`` | | | | (default), | -| | | ``style.filltype.even_odd`` | +| | | ``style.fillrule.even_odd`` | +----------------------------+---------------------------------+------------------------------------+ | :class:`deformer.deformer` | operations changing the shape | :class:`deformer.cycloid`, | | | of the path | :class:`deformer.smoothed` | @@ -325,14 +326,16 @@ | | | :class:`text.size`, | | | | :class:`text.parbox` | +----------------------------+---------------------------------+------------------------------------+ -| :class:`trafo.trafo` | ransformations applied when | :class:`trafo.mirror`, | +| :class:`trafo.trafo` | transformations applied when | :class:`trafo.mirror`, | | | drawing object | :class:`trafo.rotate`, | | | | :class:`trafo.scale`, | | | | :class:`trafo.slant`, | | | | :class:`trafo.translate` | +----------------------------+---------------------------------+------------------------------------+ -XXX specify which classes in the table are in fact instances +.. todo:: + + specify which classes in the table are in fact instances Note that operations usually allow for certain attribute categories only. For example when stroking a path, text attributes are not allowed, while stroke @@ -362,5 +365,7 @@ c.stroke(path, [deco.earrow(angle=30)]) -XXX changeable attributes +.. todo:: + + changeable attributes diff -Nru pyx-0.11.1/manual/intro.rst pyx-0.12.1/manual/intro.rst --- pyx-0.11.1/manual/intro.rst 2011-05-20 13:01:14.000000000 +0000 +++ pyx-0.12.1/manual/intro.rst 2012-10-16 20:56:09.000000000 +0000 @@ -21,7 +21,7 @@ Organisation of the PyX package =============================== -The PyX package is split in several modules, which can be categorised in the +The PyX package is split into several modules, which can be categorised in the following groups +----------------------------------+------------------------------------------+ @@ -48,5 +48,5 @@ at the beginning of the Python program. However, in order to prevent namespace pollution, you may also simply use ``import pyx``. Throughout this manual, we -shall always assume the presence of the above given import line.a +shall always assume the presence of the above given import line. diff -Nru pyx-0.11.1/manual/manual.rst pyx-0.12.1/manual/manual.rst --- pyx-0.11.1/manual/manual.rst 2011-05-20 13:01:14.000000000 +0000 +++ pyx-0.12.1/manual/manual.rst 2012-10-16 20:56:09.000000000 +0000 @@ -1,3 +1,4 @@ + ************************ PyX Reference Manual ************************ @@ -17,6 +18,8 @@ path + metapost + deformer canvas diff -Nru pyx-0.11.1/manual/metapost.rst pyx-0.12.1/manual/metapost.rst --- pyx-0.11.1/manual/metapost.rst 1970-01-01 00:00:00.000000000 +0000 +++ pyx-0.12.1/manual/metapost.rst 2012-10-16 20:56:09.000000000 +0000 @@ -0,0 +1,128 @@ +.. module:: metapost.path + +=========================== +Module :mod:`metapost.path` +=========================== + +.. sectionauthor:: Michael Schindler + +The :mod:`metapost` subpackage provides some of the path functionality of the +MetaPost program. The :mod:`metapost.path` presents the path construction +facility of MetaPost. + +Similarly to the :mod:`normpath`, there is a short length *epsilon* (always in +Postscript points pt) used as accuracy of numerical operations, such as +calculating angles from short path elements, or for omitting such short path +elements, etc. The default value is :math:`10^{-5}` and can be changed using +the module function :func:`metapost.set`. + + +Class :class:`path` --- MetaPost-like paths +------------------------------------------- + +.. class:: path(pathitems, epsilon=None) + + This class represents a MetaPost-like path which is created from the given + list of knots and curves/lines. It can find an optimal way through given + points. + + At points (knots), you can either specify a given tangent direction (angle + in degrees) or a certain *curlyness* (relative to the curvature at the other + end of a curve), or nothing. In the latter case, both the tangent and the + *mock* curvature (an approximation to the real curvature, introduced by J. D. + Hobby in MetaPost) will be continuous. + + The shape of the cubic Bezier curves between two points is controlled by + its *tension*, unless you choose to set the control points manually. + + All possible path items are described below. They are either :ref:`knots` or + :ref:`links`. Note that there is no explicit `closepath` class. Whether the + path is open or closed depends on the type of knots used, begin endpoints or + not. Note also that the number of knots and links must be equal for closed + paths, and that you cannot create a path comprising closed subpaths. + + The *epsilon* argument governs the accuracy of the calculations implied in + creating the path (see above). The value *None* means fallback to the + default epsilon of the module. + +Instances of the class :class:`path` inherit all properties of the Postscript +paths in :mod:`path`. + + +.. _knots: + +Knots +----- + +.. class:: beginknot(x, y, curl=1, angle=None) + + The first knot, starting an open path at the coordinates (*x*, *y*). The + properties of the curve in that point can either be given by its curlyness + (default) or the angle of its tangent vector (in degrees). The *curl* + parameter is (as in MetaPost) the ratio of the curvatures at this point and + at the other point of the curve connecting it. + +.. class:: startknot(x, y, curl=1, angle=None) + + Synonym for :class:`beginknot`. + +.. class:: endknot(x, y, curl=1, angle=None) + + The last knot of an open path. Curlyness and angle are the same as in + :class:`beginknot`. + +.. class:: smoothknot(x, y) + + This knot is the standard knot of MetaPost. It guarantees continuous tangent + vectors and *mock curvatures* of the two curves it connects. + + Note: If one of the links is a line, the knot is changed to a + :class:`roughknot` with either a specified angle (if the *keepangles* + parameter is set in the line) or with *curl=1*. + +.. class:: roughknot(x, y, left_curl=1, right_curl=None, left_angle=None, right_angle=None) + + This knot is a possibly non-smooth knot, connecting two curves or lines. At + each side of the knot (left/right) you can specify either the curlyness or + the tangent angle. + + Note: If one of the links is a line with the *keepangles* parameter set, the + angles will be set eplicitly, regardless of any curlyness set. + +.. class:: knot(x, y) + + Synonym for :class:`smoothknot`. + + +.. _links: + +Links +----- + +.. class:: line(keepangles=False) + + A straight line which corresponds to the MetaPost command "--". The option + *keepangles* will guarantee a continuous tangent. (The curvature may become + discontinuous, however.) This behavior is achieved by turning adjacent knots + into roughknots with specified angles. Note that a smoothknot and a + roughknot with given curlyness do behave differently near a line. + +.. class:: tensioncurve(ltension=1, latleast=False, rtension=None, ratleast=None) + + The standard type of curve in MetaPost. It corresponds to the MetaPost + command ".." or to "..." if the *atleast* parameters are set to True. The + tension parameters indicate the tensions at the beginning (l) and the end + (r) of the curve. Set the parameters (l/r)atleast to True if you want to + avoid inflection points. + +.. class:: controlcurve(lcontrol, rcontrol) + + A cubic Bezier curve which has its control points explicity set, similar to + the :class:`path.curveto` class of the Postscript paths. The control points + at the beginning (l) and the end (r) must be coordinate pairs (x, y). + +.. class:: curve(ltension=1, latleast=False, rtension=None, ratleast=None) + + Synonym for :class:`tensioncurve`. + + diff -Nru pyx-0.11.1/manual/path.rst pyx-0.12.1/manual/path.rst --- pyx-0.11.1/manual/path.rst 2011-05-20 13:01:14.000000000 +0000 +++ pyx-0.12.1/manual/path.rst 2012-10-16 20:56:09.000000000 +0000 @@ -1,3 +1,4 @@ + .. module:: path ================== @@ -11,6 +12,8 @@ the present section. +.. _postscript_like_paths: + Class :class:`path` --- PostScript-like paths --------------------------------------------- @@ -36,39 +39,34 @@ .. method:: path.arclen() - Returns the total arc length of the path.\ :math:`^\dagger` + Returns the total arc length of the path. [#normpathconvert]_ .. method:: path.arclentoparam(lengths) - Returns the parameter value(s) corresponding to the arc length(s) *lengths*.\ - :math:`^\dagger` + Returns the parameter value(s) corresponding to the arc length(s) *lengths*. + [#normpathconvert]_ .. method:: path.at(params) Returns the coordinates (as 2-tuple) of the path point(s) corresponding to the - parameter value(s) *params*.\ :math:`^\ddagger` :math:`^\dagger` + parameter value(s) *params*. [#normpathconvert]_ [#value_or_list]_ .. method:: path.atbegin() - Returns the coordinates (as 2-tuple) of the first point of the path.\ - :math:`^\dagger` + Returns the coordinates (as 2-tuple) of the first point of the path. [#normpathconvert]_ .. method:: path.atend() - Returns the coordinates (as 2-tuple) of the end point of the path.\ - :math:`^\dagger` + Returns the coordinates (as 2-tuple) of the end point of the path. [#normpathconvert]_ .. method:: path.bbox() - Returns the bounding box of the path. Note that this returned bounding box may - be too large, if the path contains any :class:`curveto` elements, since for - these the control box, i.e., the bounding box enclosing the control points of - the Bézier curve is returned. + Returns the bounding box of the path. .. method:: path.begin() @@ -77,12 +75,12 @@ point in the path. -.. method:: path.curveradius(param=None, arclen=None) +.. method:: path.curveradius(params) Returns the curvature radius/radii (or None if infinite) at parameter value(s) - params.\ :math:`^\ddagger` This is the inverse of the curvature at this + *params*. [#value_or_list]_ This is the inverse of the curvature at this parameter. Note that this radius can be negative or positive, depending on the - sign of the curvature.\ :math:`^\dagger` + sign of the curvature. [#normpathconvert]_ .. method:: path.end() @@ -99,30 +97,32 @@ .. method:: path.intersect(opath) Returns a tuple consisting of two lists of parameter values corresponding to the - intersection points of the path with the other path *opath*, respectively.\ - :math:`^\dagger` For intersection points which are not farther apart then - *epsilon* points, only one is returned. + intersection points of the path with the other path *opath*, respectively. + [#normpathconvert]_ For intersection points which are not farther apart then + *epsilon* (defaulting to :math:`10^{-5}` PostScript points), only one is returned. .. method:: path.joined(opath) Appends *opath* to the end of the path, thereby merging the last subpath (which must not be closed) of the path with the first sub path of *opath* and returns - the resulting new path.\ :math:`^\dagger` + the resulting new path. [#normpathconvert]_ Instead of using the + :meth:`joined` method, you can also join two paths together with help of the + ``<<`` operator, for instance ``p = p1 << p2``. .. method:: path.normpath(epsilon=None) Returns the equivalent :class:`normpath`. For the conversion and for later - calculations with this :class:`normpath` and accuracy of *epsilon* points is - used. If *epsilon* is *None*, the global *epsilon* of the :mod:`path` module is + calculations with this :class:`normpath` an accuracy of *epsilon* is used. + If *epsilon* is *None*, the global *epsilon* of the :mod:`path` module is used. .. method:: path.paramtoarclen(params) - Returns the arc length(s) corresponding to the parameter value(s) *params*.\ - :math:`^\ddagger` :math:`^\dagger` + Returns the arc length(s) corresponding to the parameter value(s) *params*. + [#value_or_list]_ [#normpathconvert]_ .. method:: path.range() @@ -132,59 +132,59 @@ .. method:: path.reversed() - Returns the reversed path.\ :math:`^\dagger` + Returns the reversed path. [#normpathconvert]_ .. method:: path.rotation(params) - Returns (a) rotations(s) which (each), which rotate the x-direction to the - tangent and the y-direction to the normal at that param.\ :math:`^\dagger` + Returns a transformation or a list of transformations, which rotate the + x-direction to the tangent vector and the y-direction to the normal vector + at the parameter value(s) *params*. [#value_or_list]_ [#normpathconvert]_ .. method:: path.split(params) Splits the path at the parameter values *params*, which have to be sorted in ascending order, and returns a corresponding list of :class:`normpath` - instances.\ :math:`^\dagger` + instances. [#normpathconvert]_ .. method:: path.tangent(params, length=1) - Return (a) :class:`line` instance(s) corresponding to the tangent vector(s) to - the path at the parameter value(s) *params*.\ :math:`^\ddagger` The tangent - vector will be scaled to the length *length*.\ :math:`^\dagger` + Return a :class:`line` instance or a list of :class:`line` instances, + corresponding to the tangent vectors at the parameter value(s) *params*. + [#value_or_list]_ The tangent vector will be scaled to the length *length*. + [#normpathconvert]_ .. method:: path.trafo(params) - Returns (a) trafo(s) which (each) translate to a point on the path corresponding - to the param, rotate the x-direction to the tangent and the y-direction to the - normal in that point.\ :math:`^\dagger` + Returns a transformation or a list of tranformations, which translate the + origin to a point on the path corresponding to parameter value(s) *params* + and rotate the x-direction to the tangent vector and the y-direction to the + normal vector. [#normpathconvert]_ .. method:: path.transformed(trafo) Returns the path transformed according to the linear transformation *trafo*. - Here, ``trafo`` must be an instance of the :class:`trafo.trafo` class.\ - :math:`^\dagger` + Here, ``trafo`` must be an instance of the :class:`trafo.trafo` class. + [#normpathconvert]_ -Some notes on the above: -* The :math:`\dagger` denotes methods which require a prior conversion of the - path into a :class:`normpath` instance. This is done automatically (using the - precision *epsilon* set globally using :meth:`path.set`). If you need a - different *epsilon* for a normpath, you also can perform the conversion - manually. - -* Instead of using the :meth:`joined` method, you can also join two paths - together with help of the ``<<`` operator, for instance ``p = p1 << p2``. - -* :math:`^\ddagger` In these methods, *params* may either be a single value or a - list. In the latter case, the result of the method will be a list consisting of - the results for every parameter. The parameter itself may either be a length - (or a number which is then interpreted as a user length) or an instance of the - class :class:`normpathparam`. In the former case, the length refers to the arc - length along the path. +.. [#normpathconvert] + This method requires a prior conversion of the path into a :class:`normpath` + instance. This is done automatically (using the precision *epsilon* set + globally using :meth:`path.set`). If you need a different *epsilon* for a + normpath, you also can perform the conversion manually. + +.. [#value_or_list] + In these methods, *params* may either be a single value or a + list. In the latter case, the result of the method will be a list consisting of + the results for each parameter. The parameter itself may either be a length + (or a number which is then interpreted as a user length) or an instance of the + class :class:`normpathparam`. In the former case, the length refers to the arc + length along the path. .. _path_pathitem: @@ -225,7 +225,7 @@ .. class:: rlineto(dx, dy) - Path element which appends a straight line from the current point to the a point + Path element which appends a straight line from the current point to the point with relative coordinates (*dx*, *dy*), which becomes the new current point. For the construction of arc segments, the following three operations are @@ -237,28 +237,26 @@ Path element which appends an arc segment in counterclockwise direction with absolute coordinates (*x*, *y*) of the center and radius *r* from *angle1* to *angle2* (in degrees). If before the operation, the current point is defined, a - straight line is from the current point to the beginning of the arc segment is + straight line from the current point to the beginning of the arc segment is prepended. Otherwise, a subpath, which thus is the first one in the path, is opened. After the operation, the current point is at the end of the arc segment. .. class:: arcn(x, y, r, angle1, angle2) - Path element which appends an arc segment in clockwise direction with absolute - coordinates (*x*, *y*) of the center and radius *r* from *angle1* to *angle2* - (in degrees). If before the operation, the current point is defined, a straight - line is from the current point to the beginning of the arc segment is prepended. - Otherwise, a subpath, which thus is the first one in the path, is opened. After - the operation, the current point is at the end of the arc segment. + Same as :class:`arc` but in clockwise direction. .. class:: arct(x1, y1, x2, y2, r) - Path element which appends an arc segment of radius *r* connecting between - (*x1*, *y1*) and (*x2*, *y2*). --- - -Bézier curves can be constructed using: \ + Path element consisting of a line followed by an arc of radius *r*. The arc + is part of the circle inscribed to the angle at *x1*, *y1* given by lines in + the directions to the current point and to *x2*, *y2*. The initial line + connects the current point to the point where the circle touches the line + through the current point and *x1*, *y1*. The arc then continues to the + point where the circle touches the line through *x1*, *y1* and *x2*, *y2*. +Bézier curves can be constructed using: .. class:: curveto(x1, y1, x2, y2, x3, y3) @@ -292,15 +290,15 @@ .. class:: multilineto_pt(points_pt) Path element which appends straight line segments starting from the current - point and going through the list of points given in the *points_pt* argument. - All coordinates have to be given in PostScript points. + point and going through the list of points given in the *points_pt* + argument. All coordinates have to be given in PostScript points. .. class:: multicurveto_pt(points_pt) - Path element which appends Bézier curve segments starting from the current point - and going through the list of each three control points given in the *points_pt* - argument. Thus, *points_pt* must be a sequence of 6-tuples. + Path element which appends Bézier curve segments starting from the current + point. *points_pt* is a sequence of 6-tuples containing the coordinates of + the two control points and the end point of a multicurveto segment. .. _path_normpath: @@ -309,37 +307,36 @@ ----------------------- The :class:`normpath` class is used internally for all non-trivial path -operations, i.e. the ones marked by a :math:`\dagger` in the description of the -:class:`path` above. It represents a path as a list of subpaths, which are +operations, cf. footnote [#normpathconvert]_ in Sect. :ref:`postscript_like_paths`. +It represents a path as a list of subpaths, which are instances of the class :class:`normsubpath`. These :class:`normsubpath`\ s themselves consist of a list of :class:`normsubpathitems` which are either straight lines (:class:`normline`) or Bézier curves (:class:`normcurve`). -A given path can easily be converted to the corresponding :class:`normpath` -using the method with this name:: +A given path ``p`` can easily be converted to the corresponding +:class:`normpath` ``np`` by:: np = p.normpath() -Additionally, you can specify the accuracy (in points) which is used in all -:class:`normpath` calculations by means of the argument *epsilon*, which -defaults to to :math:`10^{-5}` points. This default value can be changed using -the module function :func:`path.set`. +Additionally, the accuracy is used in all :class:`normpath` calculations can be +specified by means of the argument *epsilon*, which defaults to to +:math:`10^{-5}` where units of PostScript points are understood. This default +value can be changed using the module function :func:`path.set`. To construct a :class:`normpath` from a list of :class:`normsubpath` instances, -you pass them to the :class:`normpath` constructor: - +they are passed to the :class:`normpath` constructor: .. class:: normpath(normsubpaths=[]) Construct a :class:`normpath` consisting of *subnormpaths*, which is a list of :class:`subnormpath` instances. -Instances of :class:`normpath` offers all methods of regular :class:`path`\ s, +Instances of :class:`normpath` offer all methods of regular :class:`path` instances, which also have the same semantics. An exception are the methods :meth:`append` and :meth:`extend`. While they allow for adding of instances of :class:`subnormpath` to the :class:`normpath` instance, they also keep the functionality of a regular path and allow for regular path elements to be -appended. The later are converted to the proper normpath representation during +appended. The latter are converted to the proper normpath representation during addition. In addition to the :class:`path` methods, a :class:`normpath` instance also @@ -377,7 +374,8 @@ list of :class:`normsubpathitem` instances. If *closed* is set, the :class:`normsubpath` will be closed, thereby appending a straight line segment from the first to the last point, if it is not already present. All calculations - with the :class:`normsubpath` are performed with an accuracy of *epsilon*. + with the :class:`normsubpath` are performed with an accuracy of *epsilon* + (in units of PostScript points). Most :class:`normsubpath` methods behave like the ones of a :class:`path`. @@ -386,22 +384,23 @@ .. method:: normsubpath.append(anormsubpathitem) - Append the *anormsubpathitem* to the end of the :class:`normsubpath` instance. + Append the *normsubpathitem* to the end of the :class:`normsubpath` instance. This is only possible if the :class:`normsubpath` is not closed, otherwise an - exception is raised. + :exc:`NormpathException` is raised. .. method:: normsubpath.extend(normsubpathitems) Extend the :class:`normsubpath` instances by *normsubpathitems*, which has to be a list of :class:`normsubpathitem` instances. This is only possible if the - :class:`normsubpath` is not closed, otherwise an exception is raised. + :class:`normsubpath` is not closed, otherwise an :exc:`NormpathException` is + raised. .. method:: normsubpath.close() - Close the :class:`normsubpath` instance, thereby appending a straight line - segment from the first to the last point, if it is not already present. + Close the :class:`normsubpath` instance by appending a straight line + segment from the first to the last point, if not already present. .. _path_predefined: @@ -410,7 +409,7 @@ ---------------- -For convenience, some oft-used paths are already predefined. All of them are +For convenience, some often used paths are already predefined. All of them are subclasses of the :class:`path` class. diff -Nru pyx-0.11.1/manual/pathstyles.py pyx-0.12.1/manual/pathstyles.py --- pyx-0.11.1/manual/pathstyles.py 2011-05-18 09:09:39.000000000 +0000 +++ pyx-0.12.1/manual/pathstyles.py 2012-10-16 20:56:09.000000000 +0000 @@ -79,5 +79,4 @@ drawstyle("dash((1, 2, 3), rellengths=1)") -c.writeEPSfile("pathstyles") -c.writePDFfile("pathstyles") +c.writePDFfile() diff -Nru pyx-0.11.1/manual/pattern.rst pyx-0.12.1/manual/pattern.rst --- pyx-0.11.1/manual/pattern.rst 2011-05-20 13:01:14.000000000 +0000 +++ pyx-0.12.1/manual/pattern.rst 2012-10-16 21:13:30.000000000 +0000 @@ -1,3 +1,4 @@ + .. module:: pattern ********************* @@ -7,7 +8,7 @@ .. sectionauthor:: Jörg Lehmann -This module contains the :class:`pattern` class, whichs allows the definition of +This module contains the :class:`pattern.pattern` class, whichs allows the definition of PostScript Tiling patterns (cf. Sect. 4.9 of the PostScript Language Reference Manual) which may then be used to fill paths. In addition, a number of predefined hatch patterns are included. @@ -16,7 +17,7 @@ Class :class:`pattern` ====================== -The classes :class:`pattern` and :class:`canvas` differ only in their +The classes :class:`pattern.pattern` and :class:`canvas.canvas` differ only in their constructor and in the absence of a :meth:`writeEPSfile` method in the former. The :class:`pattern` constructor accepts the following keyword arguments: diff -Nru pyx-0.11.1/manual/radii.py pyx-0.12.1/manual/radii.py --- pyx-0.11.1/manual/radii.py 2011-05-18 09:09:39.000000000 +0000 +++ pyx-0.12.1/manual/radii.py 2012-10-16 20:56:09.000000000 +0000 @@ -12,5 +12,4 @@ isectx, isecty = circle.at(isect) c.stroke(path.line(0, 0, isectx, isecty)) -c.writeEPSfile("radii") -c.writePDFfile("radii") +c.writePDFfile() diff -Nru pyx-0.11.1/manual/radii2.py pyx-0.12.1/manual/radii2.py --- pyx-0.11.1/manual/radii2.py 2011-05-18 09:09:39.000000000 +0000 +++ pyx-0.12.1/manual/radii2.py 2012-10-16 20:56:09.000000000 +0000 @@ -15,7 +15,7 @@ segment = line2 << arc -c.fill(segment, [color.grey(0.9)]) +c.fill(segment, [color.grey(0.5)]) c.stroke(circle, [style.linewidth.Thick]) c.stroke(line, [style.linewidth.Thick]) @@ -23,5 +23,4 @@ for isect in isects_circle: c.stroke(path.line(0, 0, *circle.at(isect))) -c.writeEPSfile("radii2") -c.writePDFfile("radii2") +c.writePDFfile() diff -Nru pyx-0.11.1/manual/rects.py pyx-0.12.1/manual/rects.py --- pyx-0.11.1/manual/rects.py 2011-05-18 09:09:39.000000000 +0000 +++ pyx-0.12.1/manual/rects.py 2012-10-16 20:56:09.000000000 +0000 @@ -24,8 +24,7 @@ c.stroke(rect3, [trafo.scale(2).translated(8, 0), style.linewidth.THICK]) c.text(9, -0.7, "(c)", [text.halign.center]) -c.stroke(rect3, [trafo.scale(2).translated(12, 0), style.linewidth.THICK, deco.filled([color.grey(0.95)])]) +c.stroke(rect3, [trafo.scale(2).translated(12, 0), style.linewidth.THICK, deco.filled([color.grey(0.5)])]) c.text(13, -0.7, "(d)", [text.halign.center]) -c.writeEPSfile("rects") -c.writePDFfile("rects") +c.writePDFfile() diff -Nru pyx-0.11.1/manual/text.rst pyx-0.12.1/manual/text.rst --- pyx-0.11.1/manual/text.rst 2011-05-20 13:01:14.000000000 +0000 +++ pyx-0.12.1/manual/text.rst 2012-10-16 20:56:09.000000000 +0000 @@ -599,8 +599,8 @@ ``defaulttexrunner.reset`` -Some internals on temporary files etc. -====================================== +Some internals on temporary files +================================= It is not totally obvious how TeX processes are supervised by PyX and why it's done that way. However there are good reasons for it and the following diff -Nru pyx-0.11.1/manual/textvalign.py pyx-0.12.1/manual/textvalign.py --- pyx-0.11.1/manual/textvalign.py 2011-05-18 09:09:39.000000000 +0000 +++ pyx-0.12.1/manual/textvalign.py 2012-10-16 20:56:09.000000000 +0000 @@ -21,5 +21,4 @@ c.stroke(path.line(0, b.bottom()-b2.bottom(), 7.2, b.bottom()-b2.bottom())) c.stroke(path.line(7.3, b.bottom()-b2.bottom(), 7.5, b.bottom()-b2.bottom()), [deco.barrow.Small]) c.text(7.7, b.bottom()-b2.bottom(), "parbox.bottom", [text.vshift.mathaxis]) -c.writeEPSfile("textvalign") -c.writePDFfile("textvalign") +c.writePDFfile() diff -Nru pyx-0.11.1/manual/trafo.rst pyx-0.12.1/manual/trafo.rst --- pyx-0.11.1/manual/trafo.rst 2011-05-20 13:01:14.000000000 +0000 +++ pyx-0.12.1/manual/trafo.rst 2012-10-17 14:47:41.000000000 +0000 @@ -1,3 +1,4 @@ + .. module:: trafo ******************************************* @@ -12,14 +13,15 @@ and mirroring. -Class trafo -=========== +Class :class:`trafo` +==================== The ``trafo`` class represents a general linear transformation, which is defined -for a vector :math:`\vec{x}` as :: +for a vector :math:`\vec{x}` as + +.. math:: - XXX: translate this math - \vec{x}' = \mathsf{A}\, \vec{x} + \vec{b}\ , + \vec{x}' = \mathsf{A}\, \vec{x} + \vec{b}\ , where :math:`\mathsf{A}` is the transformation matrix and :math:`\vec{b}` the translation vector. The transformation matrix must not be singular, *i.e.* we @@ -35,70 +37,71 @@ :math:`\mathsf{A}^{-1}` of the transformation matrix and the translation vector :math:`-\mathsf{A}^{-1}\vec{b}`. -The methods of the ``trafo`` class are summarized in the following table. +.. class:: trafo(matrix=((1,0),(0,1)), vector=(0,0)) + + create new ``trafo`` instance with transformation ``matrix`` and ``vector`` + +.. method:: apply(x, y) + + apply ``trafo`` to point vector :math:`(\mathtt{x}, \mathtt{y})`. + +.. method:: inverse() + + returns inverse transformation of ``trafo``. + +.. method:: mirrored(angle) + + returns ``trafo`` followed by mirroring at line through :math:`(0,0)` with + direction ``angle`` in degrees. + +.. method:: rotated(angle, x=None, y=None) + + returns ``trafo`` followed by rotation by ``angle`` degrees around point + :math:`(\mathtt{x}, \mathtt{y})`, or :math:`(0,0)`, if not given. + +.. method:: scaled(sx, sy=None, x=None, y=None) + + returns ``trafo`` followed by scaling with scaling factor ``sx`` in + :math:`x`\ -direction, ``sy`` in :math:`y`\ -direction + (:math:`\mathtt{sy}=\mathtt{sx}`, if not given) with scaling center + :math:`(\mathtt{x}, \mathtt{y})`, or :math:`(0,0)`, if not given. -+-----------------------------------------+----------------------------------------------+ -| ``trafo`` method | function | -+=========================================+==============================================+ -| ``__init__(matrix=((1,0),(0,1)), | create new ``trafo`` instance with | -| vector=(0,0)):`` | transformation ``matrix`` and ``vector``. | -+-----------------------------------------+----------------------------------------------+ -| ``apply(x, y)`` | apply ``trafo`` to point vector | -| | :math:`(\mathtt{x}, \mathtt{y})`. | -+-----------------------------------------+----------------------------------------------+ -| ``inverse()`` | returns inverse transformation of ``trafo``. | -+-----------------------------------------+----------------------------------------------+ -| ``mirrored(angle)`` | returns ``trafo`` followed by mirroring at | -| | line through :math:`(0,0)` with direction | -| | ``angle`` in degrees. | -+-----------------------------------------+----------------------------------------------+ -| ``rotated(angle, x=None, y=None)`` | returns ``trafo`` followed by rotation by | -| | ``angle`` degrees around point | -| | :math:`(\mathtt{x}, \mathtt{y})`, or | -| | :math:`(0,0)`, if not given. | -+-----------------------------------------+----------------------------------------------+ -| ``scaled(sx, sy=None, x=None, y=None)`` | returns ``trafo`` followed by scaling with | -| | scaling factor ``sx`` in :math:`x`\ | -| | -direction, ``sy`` in :math:`y`\ -direction | -| | (:math:`\mathtt{sy}=\mathtt{sx}`, if not | -| | given) with scaling center | -| | :math:`(\mathtt{x}, \mathtt{y})`, or | -| | :math:`(0,0)`, if not given. | -+-----------------------------------------+----------------------------------------------+ -| ``translated(x, y)`` | returns ``trafo`` followed by translation by | -| | vector :math:`(\mathtt{x}, \mathtt{y})`. | -+-----------------------------------------+----------------------------------------------+ -| ``slanted(a, angle=0, x=None, y=None)`` | returns ``trafo`` followed by XXX | -+-----------------------------------------+----------------------------------------------+ +.. method:: slanted(a, angle=0, x=None, y=None) + returns ``trafo`` followed by slant by ``angle`` around point + :math:`(\mathtt{x}, \mathtt{y})`, or :math:`(0,0)`, if not given. -Subclasses of trafo -=================== +.. method:: translated(x, y) + + returns ``trafo`` followed by translation by vector :math:`(\mathtt{x}, \mathtt{y})`. + + +Subclasses of :class:`trafo` +============================ The ``trafo`` module provides a number of subclasses of the ``trafo`` class, -each of which corresponds to one ``trafo`` method. They are listed in the -following table: +each of which corresponds to one ``trafo`` method. + +.. class:: mirror(angle) + + mirroring at line through :math:`(0,0)` with direction ``angle`` in degrees. + +.. class:: rotate(angle, x=None, y=None) + + rotation by ``angle`` degrees around point :math:`(\mathtt{x}, \mathtt{y})`, or :math:`(0,0)`, if not given. + +.. class:: scale(sx, sy=None, x=None, y=None) + + scaling with scaling factor ``sx`` in :math:`x`\ -direction, ``sy`` in + :math:`y`\ -direction (:math:`\mathtt{sy}=\mathtt{sx}`, if not given) with + scaling center :math:`(\mathtt{x}, \mathtt{y})`, or :math:`(0,0)`, if not + given. + +.. class:: slant(a, angle=0, x=None, y=None) + + slant by ``angle`` around point :math:`(\mathtt{x}, \mathtt{y})`, or :math:`(0,0)`, if not given. + +.. class:: translate(x, y) -+----------------------------------------+----------------------------------------------+ -| ``trafo`` subclass | function | -+========================================+==============================================+ -| ``mirror(angle)`` | mirroring at line through :math:`(0,0)` with | -| | direction ``angle`` in degrees. | -+----------------------------------------+----------------------------------------------+ -| ``rotate(angle, x=None, y=None)`` | rotation by ``angle`` degrees around point | -| | :math:`(\mathtt{x}, \mathtt{y})`, or | -| | :math:`(0,0)`, if not given. | -+----------------------------------------+----------------------------------------------+ -| ``scale(sx, sy=None, x=None, y=None)`` | scaling with scaling factor ``sx`` in | -| | :math:`x`\ -direction, ``sy`` in :math:`y`\ | -| | -direction (:math:`\mathtt{sy}=\mathtt{sx}`, | -| | if not given) with scaling center | -| | :math:`(\mathtt{x}, \mathtt{y})`, or | -| | :math:`(0,0)`, if not given. | -+----------------------------------------+----------------------------------------------+ -| ``translate(x, y)`` | translation by vector :math:`(\mathtt{x}, | -| | \mathtt{y})`. | -+----------------------------------------+----------------------------------------------+ -| ``slant(a, angle=0, x=None, y=None)`` | XXX | -+----------------------------------------+----------------------------------------------+ + translation by vector :math:`(\mathtt{x}, \mathtt{y})`. diff -Nru pyx-0.11.1/manual/unit.rst pyx-0.12.1/manual/unit.rst --- pyx-0.11.1/manual/unit.rst 2011-05-20 13:01:14.000000000 +0000 +++ pyx-0.12.1/manual/unit.rst 2012-10-16 20:56:09.000000000 +0000 @@ -1,6 +1,8 @@ .. module:: unit +.. _module_unit: + ****************** Module :mod:`unit` ****************** @@ -57,8 +59,8 @@ at the beginning of your program. -Class length -============ +Class :class:`length` +===================== .. class:: length(f, type="u", unit=None) diff -Nru pyx-0.11.1/pyx/__init__.py pyx-0.12.1/pyx/__init__.py --- pyx-0.11.1/pyx/__init__.py 2011-05-18 09:09:35.000000000 +0000 +++ pyx-0.12.1/pyx/__init__.py 2012-10-17 15:31:57.000000000 +0000 @@ -33,7 +33,7 @@ __version__ = version.version __all__ = ["attr", "box", "bitmap", "canvas", "color", "connector", "deco", "deformer", "document", - "epsfile", "graph", "mesh", "path", "pattern", "pdfextra", "style", "trafo", "text", "unit"] + "epsfile", "graph", "mesh", "metapost", "path", "pattern", "pdfextra", "style", "trafo", "text", "unit"] # automatically import main modules into pyx namespace diff -Nru pyx-0.11.1/pyx/bbox.py pyx-0.12.1/pyx/bbox.py --- pyx-0.11.1/pyx/bbox.py 2011-05-18 09:09:35.000000000 +0000 +++ pyx-0.12.1/pyx/bbox.py 2012-10-17 15:31:57.000000000 +0000 @@ -125,7 +125,7 @@ def includepoint_pt(self, x_pt, y_pt): if self.llx_pt is None: self.llx_pt = self.urx_pt = x_pt - self.ury_pt = self.ury_pt = y_pt + self.lly_pt = self.ury_pt = y_pt else: self.llx_pt = min(self.llx_pt, x_pt) self.lly_pt = min(self.lly_pt, y_pt) diff -Nru pyx-0.11.1/pyx/bitmap.py pyx-0.12.1/pyx/bitmap.py --- pyx-0.11.1/pyx/bitmap.py 2011-05-18 09:09:35.000000000 +0000 +++ pyx-0.12.1/pyx/bitmap.py 2012-10-17 15:31:57.000000000 +0000 @@ -1,7 +1,7 @@ # -*- encoding: utf-8 -*- # # -# Copyright (C) 2004-2011 André Wobst +# Copyright (C) 2004-2012 André Wobst # Copyright (C) 2011 Michael Schindler # # This file is part of PyX (http://pyx.sourceforge.net/). @@ -278,41 +278,16 @@ file.write("\n" "endstream\n") +class bitmap_trafo(canvasitem.canvasitem): -class bitmap_pt(canvasitem.canvasitem): - - def __init__(self, xpos_pt, ypos_pt, image, width_pt=None, height_pt=None, ratio=None, + def __init__(self, trafo, image, PSstoreimage=0, PSmaxstrlen=4093, PSbinexpand=1, compressmode="Flate", flatecompresslevel=6, dctquality=75, dctoptimize=0, dctprogression=0): - self.xpos_pt = xpos_pt - self.ypos_pt = ypos_pt + self.pdftrafo = trafo self.image = image - self.imagewidth, self.imageheight = image.size - if width_pt is not None or height_pt is not None: - self.width_pt = width_pt - self.height_pt = height_pt - if self.width_pt is None: - if ratio is None: - self.width_pt = self.height_pt * self.imagewidth / float(self.imageheight) - else: - self.width_pt = ratio * self.height_pt - elif self.height_pt is None: - if ratio is None: - self.height_pt = self.width_pt * self.imageheight / float(self.imagewidth) - else: - self.height_pt = (1.0/ratio) * self.width_pt - elif ratio is not None: - raise ValueError("can't specify a ratio when setting width_pt and height_pt") - else: - if ratio is not None: - raise ValueError("must specify width_pt or height_pt to set a ratio") - widthdpi, heightdpi = image.info["dpi"] # fails when no dpi information available - self.width_pt = 72.0 * self.imagewidth / float(widthdpi) - self.height_pt = 72.0 * self.imageheight / float(heightdpi) - self.PSstoreimage = PSstoreimage self.PSmaxstrlen = PSmaxstrlen self.PSbinexpand = PSbinexpand @@ -352,7 +327,7 @@ alpha = True else: bands = data.split() - alpha = band[0] + alpha = bands[0] data = image(self.imagewidth, self.imageheight, mode, "".join(["".join(values) for values in zip(*[band.tostring() @@ -411,15 +386,16 @@ return mode, data, alpha, palettemode, palettedata def bbox(self): - return bbox.bbox_pt(self.xpos_pt, self.ypos_pt, - self.xpos_pt+self.width_pt, self.ypos_pt+self.height_pt) + bb = bbox.empty() + bb.includepoint_pt(*self.pdftrafo.apply_pt(0.0, 0.0)) + bb.includepoint_pt(*self.pdftrafo.apply_pt(0.0, 1.0)) + bb.includepoint_pt(*self.pdftrafo.apply_pt(1.0, 0.0)) + bb.includepoint_pt(*self.pdftrafo.apply_pt(1.0, 1.0)) + return bb def processPS(self, file, writer, context, registry, bbox): mode, data, alpha, palettemode, palettedata = self.imagedata(True) - imagematrixPS = (trafo.mirror(0) - .translated_pt(-self.xpos_pt, self.ypos_pt+self.height_pt) - .scaled_pt(self.imagewidth/self.width_pt, self.imageheight/self.height_pt)) - + pstrafo = trafo.translate_pt(0, -1.0).scaled(self.imagewidth, -self.imageheight)*self.pdftrafo.inverse() PSsinglestring = self.PSstoreimage and len(data) < self.PSmaxstrlen if PSsinglestring: @@ -461,7 +437,7 @@ "/Width %i\n" % self.imagewidth) file.write("/Height %i\n" % self.imageheight) file.write("/BitsPerComponent 8\n" - "/ImageMatrix %s\n" % imagematrixPS) + "/ImageMatrix %s\n" % pstrafo) file.write("/Decode %s\n" % decodestrings[mode]) file.write("/DataSource ") @@ -488,7 +464,7 @@ "/Width %i\n" % self.imagewidth) file.write("/Height %i\n" % self.imageheight) file.write("/BitsPerComponent 8\n" - "/ImageMatrix %s\n" % imagematrixPS) + "/ImageMatrix %s\n" % pstrafo) file.write("/Decode [1 0]\n" ">>\n" "/InterleaveType 1\n" @@ -501,6 +477,7 @@ file.write("%%%%BeginData: %i ASCII Lines\n" "image\n" % (asciihexlines(len(data)) + 1)) asciihexstream(file, data) + file.write(">\n") else: # the datasource is currentstream (plus some filters) file.write("%%%%BeginData: %i ASCII Lines\n" @@ -525,15 +502,40 @@ self.compressmode or self.imagecompressed, data, alpha, registry)) bbox += self.bbox() - imagematrixPDF = (trafo.scale_pt(self.width_pt, self.height_pt) - .translated_pt(self.xpos_pt, self.ypos_pt)) file.write("q\n") - imagematrixPDF.processPDF(file, writer, context, registry, bbox) + self.pdftrafo.processPDF(file, writer, context, registry, bbox) file.write("/%s Do\n" % name) file.write("Q\n") +class bitmap_pt(bitmap_trafo): + + def __init__(self, xpos_pt, ypos_pt, image, width_pt=None, height_pt=None, ratio=None, **kwargs): + imagewidth, imageheight = image.size + if width_pt is not None or height_pt is not None: + if width_pt is None: + if ratio is None: + width_pt = height_pt * imagewidth / float(imageheight) + else: + width_pt = ratio * height_pt + elif height_pt is None: + if ratio is None: + height_pt = width_pt * imageheight / float(imagewidth) + else: + height_pt = (1.0/ratio) * width_pt + elif ratio is not None: + raise ValueError("can't specify a ratio when setting width_pt and height_pt") + else: + if ratio is not None: + raise ValueError("must specify width_pt or height_pt to set a ratio") + widthdpi, heightdpi = image.info["dpi"] # fails when no dpi information available + width_pt = 72.0 * imagewidth / float(widthdpi) + height_pt = 72.0 * imageheight / float(heightdpi) + + bitmap_trafo.__init__(self, trafo.trafo_pt(((float(width_pt), 0.0), (0.0, float(height_pt))), (float(xpos_pt), float(ypos_pt))), image, **kwargs) + + class bitmap(bitmap_pt): def __init__(self, xpos, ypos, image, width=None, height=None, **kwargs): diff -Nru pyx-0.11.1/pyx/canvas.py pyx-0.12.1/pyx/canvas.py --- pyx-0.11.1/pyx/canvas.py 2011-05-18 09:09:35.000000000 +0000 +++ pyx-0.12.1/pyx/canvas.py 2012-10-17 15:31:57.000000000 +0000 @@ -1,8 +1,8 @@ # -*- encoding: utf-8 -*- # # -# Copyright (C) 2002-2011 Jörg Lehmann -# Copyright (C) 2002-2011 André Wobst +# Copyright (C) 2002-2012 Jörg Lehmann +# Copyright (C) 2002-2012 André Wobst # # This file is part of PyX (http://pyx.sourceforge.net/). # @@ -25,10 +25,28 @@ A canvas holds a collection of all elements and corresponding attributes to be displayed. """ -import os, tempfile +import cStringIO, os, sys, string, tempfile, warnings import attr, canvasitem, deco, deformer, document, font, pycompat, style, trafo import bbox as bboxmodule +def _wrappedindocument(method): + def wrappedindocument(self, file=None, **kwargs): + page_kwargs = {} + write_kwargs = {} + for name, value in kwargs.items(): + if name.startswith("page_"): + page_kwargs[name[5:]] = value + elif name.startswith("write_"): + write_kwargs[name[6:]] = value + else: + warnings.warn("Keyword argument %s of %s method should be prefixed with 'page_'" % + (name, method.__name__), DeprecationWarning) + page_kwargs[name] = value + d = document.document([document.page(self, **page_kwargs)]) + self.__name__ = method.__name__ + self.__doc__ = method.__doc__ + return method(d, file, **write_kwargs) + return wrappedindocument # # clipping class @@ -64,7 +82,7 @@ # general canvas class # -class _canvas(canvasitem.canvasitem): +class canvas(canvasitem.canvasitem): """a canvas holds a collection of canvasitems""" @@ -86,9 +104,10 @@ """ - self.items = [] - self.trafo = trafo.trafo() - self.clipbbox = None + self.items = [] + self.trafo = trafo.trafo() + self.clipbbox = None + self.layers = {} if attrs is None: attrs = [] if texrunner is not None: @@ -120,6 +139,12 @@ def __getitem__(self, i): return self.items[i] + def _repr_png_(self): + """ + Automatically represent as PNG graphic when evaluated in IPython notebook. + """ + return self.pipeGS(device="png16m", seekable=True).getvalue() + def bbox(self): """returns bounding box of canvas @@ -183,22 +208,53 @@ bbox += nbbox file.write("Q\n") # grestore - def insert(self, item, attrs=None): + def layer(self, name, above=None, below=None): + """create or get a layer with name + + A layer is a canvas itself and can be used to combine drawing + operations for ordering purposes, i.e., what is above and below each + other. The layer name is a dotted string, where dots are used to form + a hierarchy of layer groups. When inserting a layer, it is put on top + of its layer group except when another layer of this group is specified + by means of the parameters above or below. + + """ + try: + group, layer = name.split(".", 1) + except ValueError: + if not name in self.layers: + self.layers[name] = self.insert(canvas(texrunner=self.texrunner), after=above, before=below) + return self.layers[name] + else: + if not group in self.layers: + self.layers[group] = self.insert(canvas(texrunner=self.texrunner)) + return self.layers[group].layer(layer, above=above, below=below) + + def insert(self, item, attrs=None, before=None, after=None): """insert item in the canvas. - If attrs are passed, a canvas containing the item is inserted applying attrs. + If attrs are passed, a canvas containing the item is inserted applying + attrs. If one of before or after is not None, the new item is + positioned accordingly in the canvas. - returns the item + returns the item, possibly wrapped in a canvas """ if not isinstance(item, canvasitem.canvasitem): - raise RuntimeError("only instances of canvasitem.canvasitem can be inserted into a canvas") + raise ValueError("only instances of canvasitem.canvasitem can be inserted into a canvas") if attrs: - sc = _canvas(attrs) + sc = canvas(attrs) sc.insert(item) - self.items.append(sc) + item = sc + + if before is not None: + if after: + raise ValueError("before and after cannot be specified at the same time") + self.items.insert(self.items.index(before), item) + elif after is not None: + self.items.insert(self.items.index(after)+1, item) else: self.items.append(item) return item @@ -277,57 +333,120 @@ return self.insert(self.texrunner.text_pt(x, y, atext, *args)) -# -# user canvas class which adds a few convenience methods for single page output -# + writeEPSfile = _wrappedindocument(document.document.writeEPSfile) + writePSfile = _wrappedindocument(document.document.writePSfile) + writePDFfile = _wrappedindocument(document.document.writePDFfile) + writetofile = _wrappedindocument(document.document.writetofile) -def _wrappedindocument(method): - def wrappedindocument(self, file=None, **kwargs): - d = document.document([document.page(self, **kwargs)]) - self.__name__ = method.__name__ - self.__doc__ = method.__doc__ - return method(d, file) - return wrappedindocument + def _gscmd(self, device, filename, resolution=100, gscmd="gs", gsoptions="", + textalphabits=4, graphicsalphabits=4, ciecolor=False, **kwargs): -class canvas(_canvas): + allowed_chars = string.ascii_letters + string.digits + "_-./" + if filename.translate(string.maketrans("", ""), allowed_chars): + raise ValueError("for security reasons, only characters, digits and the characters '_-./' are allowed in filenames") - """a canvas holds a collection of canvasitems""" + gscmd += " -dEPSCrop -dNOPAUSE -dQUIET -dBATCH -r%i -sDEVICE=%s -sOutputFile=%s" % (resolution, device, filename) + if gsoptions: + gscmd += " %s" % gsoptions + if textalphabits is not None: + gscmd += " -dTextAlphaBits=%i" % textalphabits + if graphicsalphabits is not None: + gscmd += " -dGraphicsAlphaBits=%i" % graphicsalphabits + if ciecolor: + gscmd += " -dUseCIEColor" - writeEPSfile = _wrappedindocument(document.document.writeEPSfile) - writePSfile = _wrappedindocument(document.document.writePSfile) - writePDFfile = _wrappedindocument(document.document.writePDFfile) - writetofile = _wrappedindocument(document.document.writetofile) + return gscmd, kwargs + + def writeGSfile(self, filename=None, device=None, input="eps", **kwargs): + """ + convert EPS or PDF output to a file via Ghostscript + + If filename is None it is auto-guessed from the script name. If + filename is "-", the output is written to stdout. In both cases, a + device needs to be specified to define the format. - def pipeGS(self, filename="-", device=None, resolution=100, - gscommand="gs", gsoptions="", - textalphabits=4, graphicsalphabits=4, - ciecolor=False, input="eps", **kwargs): + If device is None, but a filename with suffix is given, PNG files will + be written using the png16m device and JPG files using the jpeg device. + """ + if filename is None: + if not sys.argv[0].endswith(".py"): + raise RuntimeError("could not auto-guess filename") + if device.startswith("png"): + filename = sys.argv[0][:-2] + "png" + elif device.startswith("jpeg"): + filename = sys.argv[0][:-2] + "jpg" + else: + filename = sys.argv[0][:-2] + device if device is None: if filename.endswith(".png"): device = "png16m" - if filename.endswith(".jpg"): + elif filename.endswith(".jpg"): device = "jpeg" - gscommand += " -dEPSCrop -dNOPAUSE -dQUIET -dBATCH -r%i -sDEVICE=%s -sOutputFile=%s" % (resolution, device, filename) - if gsoptions: - gscommand += " %s" % gsoptions - if textalphabits is not None: - gscommand += " -dTextAlphaBits=%i" % textalphabits - if graphicsalphabits is not None: - gscommand += " -dGraphicsAlphaBits=%i" % graphicsalphabits - if ciecolor: - gscommand += " -dUseCIEColor" + else: + raise RuntimeError("could not auto-guess device") + + gscmd, kwargs = self._gscmd(device, filename, **kwargs) + if input == "eps": - gscommand += " -" - pipe = pycompat.popen(gscommand, "wb") - self.writeEPSfile(pipe, **kwargs) + gscmd += " -" + stdin = pycompat.popen(gscmd, "wb") + self.writeEPSfile(stdin, **kwargs) + stdin.close() elif input == "pdf": + # PDF files need to be accesible by random access and thus we need to create + # a temporary file fd, fname = tempfile.mkstemp() f = os.fdopen(fd, "wb") - gscommand += " %s" % fname + gscmd += " %s" % fname self.writePDFfile(f, **kwargs) f.close() - os.system(gscommand) + os.system(gscmd) os.unlink(fname) else: raise RuntimeError("input 'eps' or 'pdf' expected") + + + def pipeGS(self, device, input="eps", seekable=False, **kwargs): + """ + returns a pipe with the Ghostscript output of the EPS or PDF of the canvas + + If seekable is True, a StringIO instance will be returned instead of a + pipe to allow random access. + """ + + gscmd, kwargs = self._gscmd(device, "-", **kwargs) + + if input == "eps": + gscmd += " -" + # we can safely ignore that the input and output pipes could block each other, + # because Ghostscript has to read the full input before writing the output + stdin, stdout = pycompat.popen2(gscmd) + self.writeEPSfile(stdin, **kwargs) + stdin.close() + elif input == "pdf": + # PDF files need to be accesible by random access and thus we need to create + # a temporary file + fd, fname = tempfile.mkstemp() + f = os.fdopen(fd, "wb") + gscmd += " %s" % fname + self.writePDFfile(f, **kwargs) + f.close() + stdout = pycompat.popen(gscmd, "rb") + os.unlink(fname) + else: + raise RuntimeError("input 'eps' or 'pdf' expected") + + if seekable: + # the read method of a pipe object may not return the full content + f = cStringIO.StringIO() + while True: + data = stdout.read() + if not data: + break + f.write(data) + stdout.close() + f.seek(0) + return f + else: + return stdout diff -Nru pyx-0.11.1/pyx/canvasitem.py pyx-0.12.1/pyx/canvasitem.py --- pyx-0.11.1/pyx/canvasitem.py 2011-05-18 09:09:35.000000000 +0000 +++ pyx-0.12.1/pyx/canvasitem.py 2012-10-17 15:31:57.000000000 +0000 @@ -26,6 +26,11 @@ def bbox(self): """return bounding box of canvasitem""" + # TODO: we either should raise a NotImplementedError here or return + # an empty bounding box instance, as adding empty to a bouding box is + # allowed. (We could also alter the merging behavior of bboxes to allow + # None. Currently, canvasitem instances not overwriting this bbox method + # lead to an error.) pass def processPS(self, file, writer, context, registry, bbox): diff -Nru pyx-0.11.1/pyx/color.py pyx-0.12.1/pyx/color.py --- pyx-0.11.1/pyx/color.py 2011-05-18 09:09:35.000000000 +0000 +++ pyx-0.12.1/pyx/color.py 2012-10-17 15:31:57.000000000 +0000 @@ -3,7 +3,7 @@ # # Copyright (C) 2002-2004, 2006 Jörg Lehmann # Copyright (C) 2003-2006 Michael Schindler -# Copyright (C) 2002-2007 André Wobst +# Copyright (C) 2002-2011 André Wobst # # This file is part of PyX (http://pyx.sourceforge.net/). # @@ -422,6 +422,29 @@ return self.cls(**colordict) +class rgbgradient: + + "a gradient, which takes another gradient and returns rgb colors" + + def __init__(self, gradient): + self.gradient = gradient + + def getcolor(self, param): + return self.gradient.getcolor(param).rgb() + + +class cmykgradient: + + "a gradient, which takes another gradient and returns cmyk colors" + + def __init__(self, gradient): + self.gradient = gradient + + def getcolor(self, param): + return self.gradient.getcolor(param).cmyk() + + + gradient.Gray = lineargradient(gray.white, gray.black) gradient.Grey = gradient.Gray gradient.ReverseGray = lineargradient(gray.black, gray.white) @@ -431,6 +454,11 @@ "g":(lambda x: 1.5*x**2*(1-x)**3 - 0.8*x**3*(1-x)**2 + 2.0*x**4*(1-x) + x**4), "b":(lambda x: 5*x*(1-x)**5 - 0.5*x**2*(1-x)**3 + 0.3*x*x*(1-x)**2 + 5*x**3*(1-x)**2 + 0.5*x**6)}, rgb) +gradient.YellowBlack = functiongradient({ + "r":(lambda x: 2*(1-x)*x**5 + 3.5*(1-x)**2*x**3 + 2.1*(1-x)*(1-x)*x**2 + 3.0*(1-x)**3*x**2 + (1-x)**0.5*(1-x**2)), + "g":(lambda x: 1.5*(1-x)**2*x**3 - 0.8*(1-x)**3*x**2 + 2.0*(1-x)**4*x + (1-x)**4), + "b":(lambda x: 5*(1-x)*x**5 - 0.5*(1-x)**2*x**3 + 0.3*(1-x)*(1-x)*x**2 + 5*(1-x)**3*x**2 + 0.5*(1-x)**6)}, + rgb) gradient.RedGreen = lineargradient(rgb.red, rgb.green) gradient.RedBlue = lineargradient(rgb.red, rgb.blue) gradient.GreenRed = lineargradient(rgb.green, rgb.red) @@ -453,6 +481,35 @@ gradient.ReverseRainbow = lineargradient(hsb(2.0/3.0, 1, 1), hsb(0, 1, 1)) gradient.Hue = lineargradient(hsb(0, 1, 1), hsb(1, 1, 1)) gradient.ReverseHue = lineargradient(hsb(1, 1, 1), hsb(0, 1, 1)) +rgbgradient.Rainbow = rgbgradient(gradient.Rainbow) +rgbgradient.ReverseRainbow = rgbgradient(gradient.ReverseRainbow) +rgbgradient.Hue = rgbgradient(gradient.Hue) +rgbgradient.ReverseHue = rgbgradient(gradient.ReverseHue) +cmykgradient.Rainbow = cmykgradient(gradient.Rainbow) +cmykgradient.ReverseRainbow = cmykgradient(gradient.ReverseRainbow) +cmykgradient.Hue = cmykgradient(gradient.Hue) +cmykgradient.ReverseHue = cmykgradient(gradient.ReverseHue) +def jet_r(x): + if x < 0.38: return 0 + elif x < 0.62: return (x-0.38)/(0.62-0.38) + elif x < 0.87: return 1 + else: return 0.5 + 0.5*(1-x)/(1-0.87) +def jet_g(x): + if x < 0.13: return 0 + elif x < 0.38: return (x-0.13)/(0.38-0.13) + elif x < 0.62: return 1 + elif x < 0.87: return (0.87-x)/(0.87-0.62) + else: return 0 +def jet_b(x): + if x < 0.13: return 0.5 + 0.5*x/0.13 + elif x < 0.38: return 1 + elif x < 0.62: return 1-(x-0.38)/(0.62-0.38) + else: return 0 +gradient.Jet = functiongradient({"r":jet_r, "g":jet_g, "b":jet_b}, rgb) +gradient.ReverseJet = functiongradient({"r":lambda x: jet_r(1-x), "g":lambda x: jet_g(1-x), "b":lambda x: jet_b(1-x)}, rgb) +cmykgradient.Jet = cmykgradient(gradient.Jet) +cmykgradient.ReverseJet = cmykgradient(gradient.ReverseJet) + class PDFextgstate(pdfwriter.PDFobject): diff -Nru pyx-0.11.1/pyx/deco.py pyx-0.12.1/pyx/deco.py --- pyx-0.11.1/pyx/deco.py 2011-05-20 13:01:13.000000000 +0000 +++ pyx-0.12.1/pyx/deco.py 2012-10-17 15:31:57.000000000 +0000 @@ -388,7 +388,7 @@ # helper function which constructs the arrowhead -def _arrowhead(anormpath, arclenfrombegin, direction, size, angle, constrictionlen): +def _arrowhead(anormpath, arclenfrombegin, direction, size, angle, constriction, constrictionlen): """helper routine, which returns an arrowhead from a given anormpath @@ -397,7 +397,8 @@ -1 for an arrow pointing opposite to the direction of normpath - size: size of the arrow as arc length - angle. opening angle - - constrictionlen: None (no constriction) or arc length of constriction. + - constriction: boolean to indicate whether the constriction point is to be taken into account or not + - constrictionlen: arc length of constriction. (not used when constriction is false) """ # arc length and coordinates of tip @@ -412,7 +413,7 @@ arrowr = arrowtemplate.transformed(trafo.rotate( angle/2.0, tx, ty)) # now come the joining backward parts - if constrictionlen is not None: + if constriction: # constriction point (cx, cy) lies on path cx, cy = anormpath.at(arclenfrombegin - direction * constrictionlen) arrowcr= path.line(*(arrowr.atend() + (cx,cy))) @@ -440,6 +441,18 @@ self.angle = angle self.constriction = constriction + # calculate absolute arc length of constricition + # Note that we have to correct this length because the arrowtemplates are rotated + # by self.angle/2 to the left and right. Hence, if we want no constriction, i.e., for + # self.constriction = 1, we actually have a length which is approximately shorter + # by the given geometrical factor. + if self.constriction is not None: + self.constrictionlen = self.size * self.constriction * math.cos(math.radians(self.angle/2.0)) + else: + # if we do not want a constriction, i.e. constriction is None, we still + # need constrictionlen for cutting the path + self.constrictionlen = self.size * 1 * math.cos(math.radians(self.angle/2.0)) + def __call__(self, attrs=None, pos=None, reversed=None, size=None, angle=None, constriction=_marker): if attrs is None: attrs = self.attrs @@ -459,31 +472,19 @@ dp.ensurenormpath() anormpath = dp.path - # calculate absolute arc length of constricition - # Note that we have to correct this length because the arrowtemplates are rotated - # by self.angle/2 to the left and right. Hence, if we want no constriction, i.e., for - # self.constriction = 1, we actually have a length which is approximately shorter - # by the given geometrical factor. - if self.constriction is not None: - constrictionlen = arrowheadconstrictionlen = self.size * self.constriction * math.cos(math.radians(self.angle/2.0)) - else: - # if we do not want a constriction, i.e. constriction is None, we still - # need constrictionlen for cutting the path - constrictionlen = self.size * 1 * math.cos(math.radians(self.angle/2.0)) - arrowheadconstrictionlen = None - - arclenfrombegin = (1-self.reversed)*constrictionlen + self.pos * (anormpath.arclen() - constrictionlen) + arclenfrombegin = (1-self.reversed)*self.constrictionlen + self.pos * (anormpath.arclen() - self.constrictionlen) direction = self.reversed and -1 or 1 - arrowhead = _arrowhead(anormpath, arclenfrombegin, direction, self.size, self.angle, arrowheadconstrictionlen) + arrowhead = _arrowhead(anormpath, arclenfrombegin, direction, self.size, self.angle, + self.constriction is not None, self.constrictionlen) # add arrowhead to decoratedpath dp.ornaments.draw(arrowhead, self.attrs) # exlude part of the path from stroking when the arrow is strictly at the begin or the end if self.pos == 0 and self.reversed: - dp.excluderange(0, min(self.size, constrictionlen)) + dp.excluderange(0, min(self.size, self.constrictionlen)) elif self.pos == 1 and not self.reversed: - dp.excluderange(anormpath.end() - min(self.size, constrictionlen), anormpath.end()) + dp.excluderange(anormpath.end() - min(self.size, self.constrictionlen), anormpath.end()) arrow.clear = attr.clearclass(arrow) @@ -564,6 +565,65 @@ t.linealign(self.textdist, math.cos(angle), math.sin(angle)) dp.ornaments.insert(t) +class curvedtext(deco, attr.attr): + """a text decorator for curved text + + - text: is typeset along the path to which this decorator is applied + - relarclenpos: position for the base point of the text (default: 0) + - arlenfrombegin, arclenfromend: alternative ways of specifying the position of the base point; + use of relarclenpos, arclenfrombegin and arclenfromend is mutually exclusive + - textattrs, texrunner: standard text arguments (defaults: [] resp None) + + """ + + # defaulttextattrs = [textmodule.halign.center] # TODO: not possible due to cyclic import issue + + def __init__(self, text, textattrs=[], + relarclenpos=0.5, arclenfrombegin=None, arclenfromend=None, + texrunner=None, exclude=None): + if arclenfrombegin is not None and arclenfromend is not None: + raise ValueError("either set arclenfrombegin or arclenfromend") + self.text = text + self.textattrs = textattrs + self.relarclenpos = relarclenpos + self.arclenfrombegin = arclenfrombegin + self.arclenfromend = arclenfromend + self.texrunner = texrunner + self.exclude = exclude + + def decorate(self, dp, texrunner): + if self.texrunner: + texrunner = self.texrunner + import text as textmodule + self.defaulttextattrs = [textmodule.halign.center] + + dp.ensurenormpath() + if self.arclenfrombegin is not None: + textpos = dp.path.begin() + self.arclenfrombegin + elif self.arclenfromend is not None: + textpos = dp.path.end() - self.arclenfromend + else: + # relarcpos is used if neither arcfrombegin nor arcfromend is given + textpos = self.relarclenpos * dp.path.arclen() + + textattrs = self.defaulttextattrs + self.textattrs + t = texrunner.text(0, 0, self.text, textattrs, singlecharmode=1) + t.ensuredvicanvas() + + c = canvas.canvas() + for item in t.dvicanvas.items: + bbox = item.bbox() + if bbox: + x = item.bbox().center()[0] + atrafo = dp.path.trafo(textpos+x) + c.insert(item, [trafo.translate(-x, 0), atrafo]) + if self.exclude is not None: + dp.excluderange(textpos+bbox.left()-self.exclude, textpos+bbox.right()+self.exclude) + else: + c.insert(item) + dp.ornaments.insert(c) + + class shownormpath(deco, attr.attr): diff -Nru pyx-0.11.1/pyx/deformer.py pyx-0.12.1/pyx/deformer.py --- pyx-0.11.1/pyx/deformer.py 2011-05-18 09:09:35.000000000 +0000 +++ pyx-0.12.1/pyx/deformer.py 2012-10-17 15:31:57.000000000 +0000 @@ -30,6 +30,9 @@ def __init__(self, param): self.normsubpathitemparam = param +# None has a meaning in linesmoothed +class _marker: pass + def curvescontrols_from_endlines_pt(B, tangent1, tangent2, r1, r2, softness): # <<< # calculates the parameters for two bezier curves connecting two lines (curvature=0) # starting at B - r1*tangent1 @@ -458,7 +461,7 @@ cycloid.clear = attr.clearclass(cycloid) -class smoothed(deformer): # <<< +class cornersmoothed(deformer): # <<< """Bends corners in a normpath. @@ -492,7 +495,7 @@ obeycurv = self.obeycurv if relskipthres is None: relskipthres = self.relskipthres - return smoothed(radius=radius, softness=softness, obeycurv=obeycurv, relskipthres=relskipthres) + return cornersmoothed(radius=radius, softness=softness, obeycurv=obeycurv, relskipthres=relskipthres) def deform(self, basepath): return normpath.normpath([self.deformsubpath(normsubpath) @@ -633,6 +636,8 @@ # >>> +cornersmoothed.clear = attr.clearclass(cornersmoothed) +smoothed = cornersmoothed smoothed.clear = attr.clearclass(smoothed) class parallel(deformer): # <<< @@ -1357,4 +1362,76 @@ parallel.clear = attr.clearclass(parallel) +class linesmoothed(deformer): # <<< + + def __init__(self, tension=1, atleast=False, lcurl=1, rcurl=1): + """Tension and atleast control the tension of the replacement curves. + l/rcurl control the curlynesses at (possible) endpoints. If a curl is + set to None, the angle is taken from the original path.""" + if atleast: + self.tension = -abs(tension) + else: + self.tension = abs(tension) + self.lcurl = lcurl + self.rcurl = rcurl + + def __call__(self, tension=_marker, atleast=_marker, lcurl=_marker, rcurl=_marker): + if tension is _marker: + tension = self.tension + if atleast is _marker: + atleast = (self.tension < 0) + if lcurl is _marker: + lcurl = self.lcurl + if rcurl is _marker: + rcurl = self.rcurl + return linesmoothed(tension, atleast, lcurl, rcurl) + + def deform(self, basepath): + newnp = normpath.normpath() + for nsp in basepath.normpath().normsubpaths: + newnp += self.deformsubpath(nsp) + return newnp + + def deformsubpath(self, nsp): + import metapost.path as mppath + """Returns a path/normpath from the points in the given normsubpath""" + # TODO: epsilon ? + knots = [] + + # first point + x_pt, y_pt = nsp.atbegin_pt() + if nsp.closed: + knots.append(mppath.smoothknot_pt(x_pt, y_pt)) + elif self.lcurl is None: + rot = nsp.rotation([0])[0] + dx, dy = rot.apply_pt(1, 0) + angle = math.atan2(dy, dx) + knots.append(mppath.beginknot_pt(x_pt, y_pt, angle=angle)) + else: + knots.append(mppath.beginknot_pt(x_pt, y_pt, curl=self.lcurl)) + + # intermediate points: + for npelem in nsp[:-1]: + knots.append(mppath.tensioncurve(self.tension)) + knots.append(mppath.smoothknot_pt(*npelem.atend_pt())) + + # last point + knots.append(mppath.tensioncurve(self.tension)) + x_pt, y_pt = nsp.atend_pt() + if nsp.closed: + pass + elif self.rcurl is None: + rot = nsp.rotation([len(nsp)])[0] + dx, dy = rot.apply_pt(1, 0) + angle = math.atan2(dy, dx) + knots.append(mppath.endknot_pt(x_pt, y_pt, angle=angle)) + else: + knots.append(mppath.endknot_pt(x_pt, y_pt, curl=self.rcurl)) + + return mppath.path(knots) +# >>> + +linesmoothed.clear = attr.clearclass(linesmoothed) + + # vim:foldmethod=marker:foldmarker=<<<,>>> diff -Nru pyx-0.11.1/pyx/document.py pyx-0.12.1/pyx/document.py --- pyx-0.11.1/pyx/document.py 2011-05-18 09:09:35.000000000 +0000 +++ pyx-0.12.1/pyx/document.py 2012-10-17 15:31:57.000000000 +0000 @@ -137,6 +137,8 @@ if not sys.argv[0].endswith(".py"): raise RuntimeError("could not auto-guess filename") return open("%s.%s" % (sys.argv[0][:-3], suffix), "wb") + if file == "-": + return sys.stdout try: file.write("") return file diff -Nru pyx-0.11.1/pyx/dvi/dvifile.py pyx-0.12.1/pyx/dvi/dvifile.py --- pyx-0.11.1/pyx/dvi/dvifile.py 2011-05-18 09:09:33.000000000 +0000 +++ pyx-0.12.1/pyx/dvi/dvifile.py 2012-10-17 15:31:57.000000000 +0000 @@ -79,7 +79,14 @@ # save and restore colors -class _savecolor(canvasitem.canvasitem): +class _canvasitem(canvasitem.canvasitem): + + def bbox(self): + # TODO: see TODO in bbox method of canvasitem + return bbox.empty() + + +class _savecolor(_canvasitem): def processPS(self, file, writer, context, registry, bbox): file.write("currentcolor currentcolorspace\n") @@ -87,14 +94,15 @@ file.write("q\n") -class _restorecolor(canvasitem.canvasitem): +class _restorecolor(_canvasitem): def processPS(self, file, writer, context, registry, bbox): file.write("setcolorspace setcolor\n") def processPDF(self, file, writer, context, registry, bbox): file.write("Q\n") -class _savetrafo(canvasitem.canvasitem): + +class _savetrafo(_canvasitem): def processPS(self, file, writer, context, registry, bbox): file.write("matrix currentmatrix\n") @@ -102,7 +110,7 @@ file.write("q\n") -class _restoretrafo(canvasitem.canvasitem): +class _restoretrafo(_canvasitem): def processPS(self, file, writer, context, registry, bbox): file.write("setmatrix\n") @@ -197,7 +205,7 @@ self.activetext[2].append(char) self.pos[_POS_H] += dx - if not advancepos: + if (not advancepos) or self.singlecharmode: self.flushtext(fontmap) def usefont(self, fontnum, id1234, fontmap): @@ -220,7 +228,7 @@ # check whether it's a virtual font by trying to open it. if this fails, it is an ordinary TeX font try: - fontfile = filelocator.open(fontname, [filelocator.format.vf]) + fontfile = filelocator.open(fontname, [filelocator.format.vf], mode="rb") except IOError: afont = texfont.TeXfont(fontname, c, q/self.tfmconv, d/self.tfmconv, self.tfmconv, self.pyxconv, self.debug>1) else: @@ -418,13 +426,15 @@ else: raise DVIError - def readpage(self, pageid=None, fontmap=None): + def readpage(self, pageid=None, fontmap=None, singlecharmode=False): """ reads a page from the dvi file This routine reads a page from the dvi file which is returned as a canvas. When there is no page left in the dvifile, None is returned and the file is closed properly.""" + self.singlecharmode = singlecharmode + while 1: self.filepos = self.file.tell() cmd = self.file.readuchar() diff -Nru pyx-0.11.1/pyx/dvi/mapfile.py pyx-0.12.1/pyx/dvi/mapfile.py --- pyx-0.11.1/pyx/dvi/mapfile.py 2011-05-18 09:09:33.000000000 +0000 +++ pyx-0.12.1/pyx/dvi/mapfile.py 2012-10-17 15:31:57.000000000 +0000 @@ -22,7 +22,7 @@ import os.path, re, warnings from pyx import font, filelocator -from pyx.font import t1file, afmfile +from pyx.font import t1file, afmfile, pfmfile from pyx.dvi import encfile class UnsupportedFontFormat(Exception): @@ -128,7 +128,16 @@ try: metricfile = filelocator.open(os.path.splitext(self.fontfilename)[0], [filelocator.format.afm]) except IOError: - self._font = font.T1font(t1font, None) + try: + # fallback by using the pfm instead of the afm font metric + # (in all major TeX distributions there is no pfm file format defined by kpsewhich, but + # we can use the type1 format and search for the file including the expected suffix) + metricfile = filelocator.open("%s.pfm" % os.path.splitext(self.fontfilename)[0], [filelocator.format.type1]) + except IOError: + self._font = font.T1font(t1font) + else: + self._font = font.T1font(t1font, pfmfile.PFMfile(metricfile, t1font)) + metricfile.close() else: self._font = font.T1font(t1font, afmfile.AFMfile(metricfile)) metricfile.close() diff -Nru pyx-0.11.1/pyx/dvi/texfont.py pyx-0.12.1/pyx/dvi/texfont.py --- pyx-0.11.1/pyx/dvi/texfont.py 2011-05-20 13:01:13.000000000 +0000 +++ pyx-0.12.1/pyx/dvi/texfont.py 2012-10-17 15:31:57.000000000 +0000 @@ -173,21 +173,23 @@ def bbox(self): return self._bbox - def processPS(self, file, writer, context, registry, bbox): - bbox += self.bbox() + def _text(self, writer): if self.fontmap is not None: mapline = self.font.getMAPline(self.fontmap) else: mapline = self.font.getMAPline(writer.getfontmap()) font = mapline.getfont() - text = font.text_pt(self.x_pt, self.y_pt, self.charcodes, self.size_pt, decoding=mapline.getencoding(), slant=mapline.slant, ignorebbox=True) - text.processPS(file, writer, context, registry, bbox) + return font.text_pt(self.x_pt, self.y_pt, self.charcodes, self.size_pt, decoding=mapline.getencoding(), slant=mapline.slant, ignorebbox=True) - def processPDF(self, file, writer, context, registry, bbox): + def textpath(self): + from pyx import pswriter + return self._text(pswriter._PSwriter()).textpath() + + def processPS(self, file, writer, context, registry, bbox): bbox += self.bbox() + self._text(writer).processPS(file, writer, context, registry, bbox) - mapline = self.font.getMAPline(writer.getfontmap()) - font = mapline.getfont() - text = font.text_pt(self.x_pt, self.y_pt, self.charcodes, self.size_pt, decoding=mapline.getencoding(), slant=mapline.slant, ignorebbox=True) - text.processPDF(file, writer, context, registry, bbox) + def processPDF(self, file, writer, context, registry, bbox): + bbox += self.bbox() + self._text(writer).processPDF(file, writer, context, registry, bbox) diff -Nru pyx-0.11.1/pyx/epsfile.py pyx-0.12.1/pyx/epsfile.py --- pyx-0.11.1/pyx/epsfile.py 2011-05-18 09:09:35.000000000 +0000 +++ pyx-0.12.1/pyx/epsfile.py 2012-10-17 15:31:57.000000000 +0000 @@ -65,7 +65,7 @@ # in the DSC spec #5001, while '\n\r' *is* a *single* linebreak # according to the EPSF spec #5002 - def __init__(self, filename, typicallinelen=257): + def __init__(self, file, typicallinelen=257): """Opens the file filename for reading. typicallinelen defines the default buffer increase @@ -74,8 +74,7 @@ # note: The maximal line size in an EPS is 255 plus the # linebreak characters. However, we also handle # lines longer than that. - - self.file = open(filename, "rb") + self.file = file self.buffer = "" self.typicallinelen = typicallinelen @@ -138,10 +137,10 @@ self.file.close() -def _readbbox(filename): +def _readbbox(file): """returns bounding box of EPS file filename""" - file = linefilereader(filename) + file = linefilereader(file) # check the %! header comment if not file.readline().startswith("%!"): @@ -248,7 +247,12 @@ self.y_pt = unit.topt(y) self.filename = filename self.kpsearch = kpsearch - self.mybbox = bbox or _readbbox(self.filename) + if bbox: + self.mybbox = bbox + else: + epsfile = self.open() + self.mybbox = _readbbox(epsfile) + epsfile.close() # determine scaling in x and y direction self.scalex = self.scaley = scale @@ -307,6 +311,12 @@ if translatebbox: self.trafo = self.trafo * trafo.translate_pt(-self.mybbox.llx_pt, -self.mybbox.lly_pt) + def open(self): + if self.kpsearch: + return filelocator.open(self.filename, [filelocator.format.pict], "rb") + else: + return open(self.filename, "rb") + def bbox(self): return self.mybbox.transformed(self.trafo) @@ -315,11 +325,6 @@ registry.add(_EndEPSF) bbox += self.bbox() - if self.kpsearch: - epsfile = filelocator.open(self.filename, [filelocator.format.pict], "rb") - else: - epsfile = open(self.filename, "rb") - file.write("BeginEPSF\n") if self.clip: @@ -329,10 +334,13 @@ self.trafo.processPS(file, writer, context, registry, bbox) file.write("%%%%BeginDocument: %s\n" % self.filename) + + epsfile = self.open() file.write(epsfile.read()) + epsfile.close() + file.write("%%EndDocument\n") file.write("EndEPSF\n") - epsfile.close() def processPDF(self, file, writer, context, registry, bbox): warnings.warn("EPS file is included as a bitmap created using pipeGS") @@ -340,13 +348,8 @@ import Image c = canvas.canvas() c.insert(self) - fd, fname = tempfile.mkstemp() - f = os.fdopen(fd, "wb") - f.close() - c.pipeGS(fname, device="pngalpha", resolution=600) - i = Image.open(fname) + i = Image.open(c.pipeGS(device="pngalpha", resolution=600, seekable=True)) i.load() - os.unlink(fname) b = bitmap.bitmap_pt(self.bbox().llx_pt, self.bbox().lly_pt, i) # we slightly shift the bitmap to re-center it, as the bitmap might contain some additional border # unfortunately we need to construct another bitmap instance for that ... diff -Nru pyx-0.11.1/pyx/filelocator.py pyx-0.12.1/pyx/filelocator.py --- pyx-0.11.1/pyx/filelocator.py 2011-05-18 09:09:35.000000000 +0000 +++ pyx-0.12.1/pyx/filelocator.py 2012-10-17 15:31:57.000000000 +0000 @@ -200,7 +200,7 @@ break else: return [] - full_filename = full_filenames.split("\n")[0] + full_filename = full_filenames.split("\n")[0].rstrip("\r") def _opener(): try: return builtinopen(full_filename, mode) @@ -223,7 +223,7 @@ break else: return [] - full_filename = full_filenames.split("\n")[0] + full_filename = full_filenames.split("\n")[0].rstrip("\r") def _opener(): try: return builtinopen(full_filenames, mode) @@ -281,7 +281,7 @@ format.tfm = format("tfm", [".tfm"]) format.afm = format("afm", [".afm"]) format.fontmap = format("map", []) -format.pict = format("graphics/figure", [".eps", ".epsi"]) +format.pict = format("graphic/figure", [".eps", ".epsi"]) format.tex_ps_header = format("PostScript header", [".pro"]) # contains also: enc files format.type1 = format("type1 fonts", [".pfa", ".pfb"]) format.vf = format("vf", [".vf"]) diff -Nru pyx-0.11.1/pyx/font/afmfile.py pyx-0.12.1/pyx/font/afmfile.py --- pyx-0.11.1/pyx/font/afmfile.py 2011-05-18 09:09:35.000000000 +0000 +++ pyx-0.12.1/pyx/font/afmfile.py 2012-10-17 15:31:57.000000000 +0000 @@ -1347,21 +1347,17 @@ else: raise AFMError("Undefined state in AFM reader") - def fucking_scale(self): - # XXX XXX XXX - return 1000.0 - def width_ds(self, glyphname): return self.charmetricsdict[glyphname].widths[0][0] - def width_pt(self, glyphnames, size): - return sum([self.charmetricsdict[glyphname].widths[0][0] for glyphname in glyphnames])*size/self.fucking_scale() + def width_pt(self, glyphnames, size_pt): + return sum([self.charmetricsdict[glyphname].widths[0][0] for glyphname in glyphnames])*size_pt/1000.0 - def height_pt(self, glyphnames, size): - return max([self.charmetricsdict[glyphname].bbox[3] for glyphname in glyphnames])*size/self.fucking_scale() + def height_pt(self, glyphnames, size_pt): + return max([self.charmetricsdict[glyphname].bbox[3] for glyphname in glyphnames])*size_pt/1000.0 - def depth_pt(self, glyphnames, size): - return min([self.charmetricsdict[glyphname].bbox[1] for glyphname in glyphnames])*size/self.fucking_scale() + def depth_pt(self, glyphnames, size_pt): + return min([self.charmetricsdict[glyphname].bbox[1] for glyphname in glyphnames])*size_pt/1000.0 def resolveligatures(self, glyphnames): i = 1 @@ -1375,15 +1371,15 @@ i += 1 return glyphnames - def resolvekernings(self, glyphnames, size=None): + def resolvekernings(self, glyphnames, size_pt=None): result = [None]*(2*len(glyphnames)-1) for i, glyphname in enumerate(glyphnames): result[2*i] = glyphname if i: kernpair = self.kernpairsdict.get((glyphnames[i-1], glyphname)) if kernpair: - if size is not None: - result[2*i-1] = kernpair.x*size/self.fucking_scale() + if size_pt is not None: + result[2*i-1] = kernpair.x*size_pt/1000.0 else: result[2*i-1] = kernpair.x return result diff -Nru pyx-0.11.1/pyx/font/font.py pyx-0.12.1/pyx/font/font.py --- pyx-0.11.1/pyx/font/font.py 2011-05-18 09:09:35.000000000 +0000 +++ pyx-0.12.1/pyx/font/font.py 2012-10-17 15:31:57.000000000 +0000 @@ -21,14 +21,9 @@ # along with PyX; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA -from pyx import bbox, canvasitem, deco, path, pswriter, pdfwriter, trafo, unit -import t1file - -try: - set() -except NameError: - # Python 2.3 - from sets import Set as set +import warnings +from pyx import bbox, canvasitem, deco, path, pswriter, pdfwriter, trafo, unit, pycompat +import t1file, afmfile ############################################################################## @@ -44,8 +39,8 @@ self.type = "t1file" self.t1file = t1file self.id = t1file.name - self.glyphnames = set(glyphnames) - self.charcodes = set(charcodes) + self.glyphnames = pycompat.set(glyphnames) + self.charcodes = pycompat.set(charcodes) def merge(self, other): self.glyphnames.update(other.glyphnames) @@ -167,7 +162,7 @@ self.fontname = fontname self.basefontname = basefontname - self.charcodes = set(charcodes) + self.charcodes = pycompat.set(charcodes) self.fontdescriptor = fontdescriptor self.encoding = encoding self.metric = metric @@ -268,8 +263,8 @@ def __init__(self, t1file, glyphnames, charcodes): pdfwriter.PDFobject.__init__(self, "fontfile", t1file.name) self.t1file = t1file - self.glyphnames = set(glyphnames) - self.charcodes = set(charcodes) + self.glyphnames = pycompat.set(glyphnames) + self.charcodes = pycompat.set(charcodes) def merge(self, other): self.glyphnames.update(other.glyphnames) @@ -324,7 +319,7 @@ class T1font(font): - def __init__(self, t1file, metric): + def __init__(self, t1file, metric=None): self.t1file = t1file self.name = t1file.name self.metric = metric @@ -364,7 +359,7 @@ class T1text_pt(text_pt): - def __init__(self, font, x_pt, y_pt, charcodes, size_pt, decoding=None, slant=None, ignorebbox=False, kerning=False, ligatures=False, spaced_pt=0): + def __init__(self, font, x_pt, y_pt, charcodes, size_pt, decoding=afmfile.unicodestring, slant=None, ignorebbox=False, kerning=False, ligatures=False, spaced_pt=0): if decoding is not None: self.glyphnames = [decoding[character] for character in charcodes] self.decode = True @@ -380,6 +375,7 @@ self.kerning = kerning self.ligatures = ligatures self.spaced_pt = spaced_pt + self._textpath = None if self.kerning and not self.decode: raise ValueError("decoding required for font metric access (kerning)") @@ -390,7 +386,8 @@ def bbox(self): if self.font.metric is None: - raise ValueError("metric missing") + warnings.warn("We are about to extract the bounding box from the path of the text. This is slow and differs from the font metric information. You should provide an afm file whenever possible.") + return self.textpath().bbox() if not self.decode: raise ValueError("decoding required for font metric access (bbox)") return bbox.bbox_pt(self.x_pt, @@ -402,7 +399,7 @@ """returns the name of the encoding (in encodings) mapping self.glyphnames to codepoints If no such encoding can be found or extended, a new encoding is added to encodings """ - glyphnames = set(self.glyphnames) + glyphnames = pycompat.set(self.glyphnames) if len(glyphnames) > 256: raise ValueError("glyphs do not fit into one single encoding") for encodingname, encoding in encodings.items(): @@ -421,11 +418,8 @@ encodings[encodingname] = dict([(glyphname, i) for i, glyphname in enumerate(glyphnames)]) return encodingname - def processPS(self, file, writer, context, registry, bbox): - if not self.ignorebbox: - bbox += self.bbox() - - if writer.text_as_path: + def textpath(self): + if self._textpath is None: if self.decode: if self.kerning: data = self.font.metric.resolvekernings(self.glyphnames, self.size_pt) @@ -433,7 +427,7 @@ data = self.glyphnames else: data = self.charcodes - textpath = path.path() + self._textpath = path.path() x_pt = self.x_pt y_pt = self.y_pt for i, value in enumerate(data): @@ -443,11 +437,18 @@ else: if i: x_pt += self.spaced_pt - glyphpath, wx_pt, wy_pt = self.font.t1file.getglyphpathwxwy_pt(value, self.size_pt, convertcharcode=not self.decode) - textpath += glyphpath.transformed(trafo.translate_pt(x_pt, y_pt)) - x_pt += wx_pt - y_pt += wy_pt - deco.decoratedpath(textpath, fillstyles=[]).processPS(file, writer, context, registry, bbox) + glyphpath = self.font.t1file.getglyphpath_pt(x_pt, y_pt, value, self.size_pt, convertcharcode=not self.decode) + self._textpath += glyphpath.path + x_pt += glyphpath.wx_pt + y_pt += glyphpath.wy_pt + return self._textpath + + def processPS(self, file, writer, context, registry, bbox): + if not self.ignorebbox: + bbox += self.bbox() + + if writer.text_as_path: + deco.decoratedpath(self.textpath(), fillstyles=[]).processPS(file, writer, context, registry, bbox) else: # register resources if self.font.t1file is not None: @@ -466,7 +467,9 @@ fontname = newfontname if self.slant: - newfontmatrix = trafo.trafo_pt(matrix=((1, self.slant), (0, 1))) * self.font.t1file.fontmatrix + newfontmatrix = trafo.trafo_pt(matrix=((1, self.slant), (0, 1))) + if self.font.t1file is not None: + newfontmatrix = newfontmatrix * self.font.t1file.fontmatrix newfontname = "%s-slant%f" % (fontname, self.slant) registry.add(_ChangeFontMatrix) registry.add(PSchangefontmatrix(fontname, newfontname, newfontmatrix)) @@ -508,28 +511,7 @@ bbox += self.bbox() if writer.text_as_path: - if self.decode: - if self.kerning: - data = self.font.metric.resolvekernings(self.glyphnames, self.size_pt) - else: - data = self.glyphnames - else: - data = self.charcodes - textpath = path.path() - x_pt = self.x_pt - y_pt = self.y_pt - for i, value in enumerate(data): - if self.kerning and i % 2: - if value is not None: - x_pt += value - else: - if i: - x_pt += self.spaced_pt - glyphpath, wx_pt, wy_pt = self.font.t1file.getglyphpathwxwy_pt(value, self.size_pt, convertcharcode=not self.decode) - textpath += glyphpath.transformed(trafo.translate_pt(x_pt, y_pt)) - x_pt += wx_pt - y_pt += wy_pt - deco.decoratedpath(textpath, fillstyles=[]).processPDF(file, writer, context, registry, bbox) + deco.decoratedpath(self.textpath(), fillstyles=[]).processPDF(file, writer, context, registry, bbox) else: if self.decode: encodingname = self.getencodingname(writer.encodings.setdefault(self.font.name, {})) diff -Nru pyx-0.11.1/pyx/font/metric.py pyx-0.12.1/pyx/font/metric.py --- pyx-0.11.1/pyx/font/metric.py 2011-05-18 09:09:35.000000000 +0000 +++ pyx-0.12.1/pyx/font/metric.py 2012-10-17 15:31:57.000000000 +0000 @@ -26,19 +26,19 @@ def width_ds(self, glyphname): raise NotImplementedError() - def width_pt(self, glyphnames, size): + def width_pt(self, glyphnames, size_pt): raise NotImplementedError() - def height_pt(self, glyphnames, size): + def height_pt(self, glyphnames, size_pt): raise NotImplementedError() - def depth_pt(self, glyphnames, size): + def depth_pt(self, glyphnames, size_pt): raise NotImplementedError() def resolveligatures(self, glyphnames): return glyphnames - def resolvekernings(self, glyphnames, size=None): + def resolvekernings(self, glyphnames, size_pt=None): result = [None]*(2*len(glyphnames)-1) for i, glyphname in enumerate(glyphnames): result[2*i] = glyphname diff -Nru pyx-0.11.1/pyx/font/pfmfile.py pyx-0.12.1/pyx/font/pfmfile.py --- pyx-0.11.1/pyx/font/pfmfile.py 1970-01-01 00:00:00.000000000 +0000 +++ pyx-0.12.1/pyx/font/pfmfile.py 2012-10-17 15:31:57.000000000 +0000 @@ -0,0 +1,402 @@ +# -*- encoding: utf-8 -*- +# +# +# Copyright (C) 2007-2011 André Wobst +# +# This file is part of PyX (http://pyx.sourceforge.net/). +# +# PyX is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# PyX is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with PyX; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +import struct, re +import metric + + +ansiglyphs = {"space": 32, + "exclam": 33, + "quotedbl": 34, + "numbersign": 35, + "dollar": 36, + "percent": 37, + "ampersand": 38, + "quotesingle": 39, + "parenleft": 40, + "parenright": 41, + "asterisk": 42, + "plus": 43, + "comma": 44, + "hyphen": 45, + "period": 46, + "slash": 47, + "zero": 48, + "one": 49, + "two": 50, + "three": 51, + "four": 52, + "five": 53, + "six": 54, + "seven": 55, + "eight": 56, + "nine": 57, + "colon": 58, + "semicolon": 59, + "less": 60, + "equal": 61, + "greater": 62, + "question": 63, + "at": 64, + "A": 65, + "B": 66, + "C": 67, + "D": 68, + "E": 69, + "F": 70, + "G": 71, + "H": 72, + "I": 73, + "J": 74, + "K": 75, + "L": 76, + "M": 77, + "N": 78, + "O": 79, + "P": 80, + "Q": 81, + "R": 82, + "S": 83, + "T": 84, + "U": 85, + "V": 86, + "W": 87, + "X": 88, + "Y": 89, + "Z": 90, + "bracketleft": 91, + "backslash": 92, + "bracketright": 93, + "asciicircum": 94, + "underscore": 95, + "grave": 96, + "a": 97, + "b": 98, + "c": 99, + "d": 100, + "e":101, + "f":102, + "g":103, + "h":104, + "i":105, + "j":106, + "k":107, + "l":108, + "m":109, + "n":110, + "o":111, + "p":112, + "q":113, + "r":114, + "s":115, + "t":116, + "u":117, + "v":118, + "w":119, + "x":120, + "y":121, + "z":122, + "braceleft":123, + "bar":124, + "braceright":125, + "asciitilde":126, + "bullet":127, + "Euro":128, + "bullet":129, + "quotesinglbase":130, + "florin":131, + "quotedblbase":132, + "ellipsis":133, + "dagger":134, + "daggerdbl":135, + "circumflex":136, + "perthousand":137, + "Scaron":138, + "guilsinglleft":139, + "OE":140, + "bullet":141, + "Zcaron":142, + "bullet":143, + "bullet":144, + "quoteleft":145, + "quoteright":146, + "quotedblleft":147, + "quotedblright":148, + "bullet":149, + "endash":150, + "emdash":151, + "tilde":152, + "trademark":153, + "scaron":154, + "guilsinglright":155, + "oe":156, + "bullet":157, + "zcaron":158, + "Ydieresis":159, + "space":160, + "exclamdown":161, + "cent":162, + "sterling":163, + "currency":164, + "yen":165, + "brokenbar":166, + "section":167, + "dieresis":168, + "copyright":169, + "ordfeminine":170, + "guillemotleft":171, + "logicalnot":172, + "hyphen":173, + "registered":174, + "macron":175, + "degree":176, + "plusminus":177, + "twosuperior":178, + "threesuperior":179, + "acute":180, + "mu":181, + "paragraph":182, + "periodcentered":183, + "cedilla":184, + "onesuperior":185, + "ordmasculine":186, + "guillemotright":187, + "onequarter":188, + "onehalf":189, + "threequarters":190, + "questiondown":191, + "Agrave":192, + "Aacute":193, + "Acircumflex":194, + "Atilde":195, + "Adieresis":196, + "Aring":197, + "AE":198, + "Ccedilla":199, + "Egrave":200, + "Eacute":201, + "Ecircumflex":202, + "Edieresis":203, + "Igrave":204, + "Iacute":205, + "Icircumflex":206, + "Idieresis":207, + "Eth":208, + "Ntilde":209, + "Ograve":210, + "Oacute":211, + "Ocircumflex":212, + "Otilde":213, + "Odieresis":214, + "multiply":215, + "Oslash":216, + "Ugrave":217, + "Uacute":218, + "Ucircumflex":219, + "Udieresis":220, + "Yacute":221, + "Thorn":222, + "germandbls":223, + "agrave":224, + "aacute":225, + "acircumflex":226, + "atilde":227, + "adieresis":228, + "aring":229, + "ae":230, + "ccedilla":231, + "egrave":232, + "eacute":233, + "ecircumflex":234, + "edieresis":235, + "igrave":236, + "iacute":237, + "icircumflex":238, + "idieresis":239, + "eth":240, + "ntilde":241, + "ograve":242, + "oacute":243, + "ocircumflex":244, + "otilde":245, + "odieresis":246, + "divide":247, + "oslash":248, + "ugrave":249, + "uacute":250, + "ucircumflex":251, + "udieresis":252, + "yacute":253, + "thorn":254, + "ydieresis":255} + + +fontbboxpattern = re.compile("/FontBBox\s*\{\s*(?P(-?[0-9.]+)\s+(-?[0-9.]+)\s+(-?[0-9.]+)\s+(-?[0-9.]+))\s*\}\s*(readonly\s+)?def") + + +def _readNullString(file): + s = [] + c = file.read(1) + while c and c != "\0": + s.append(c) + c = file.read(1) + return "".join(s) + + +class PFMfile(metric.metric): + + def __init__(self, file, t1file): + # pfm is rather incomplete, the t1file instance can be used to fill the gap + (self.dfVersion, self.dfSize, self.dfCopyright, self.dfType, + self.dfPoint, self.dfVertRes, self.dfHorizRes, self.dfAscent, + self.dfInternalLeading, self.dfExternalLeading, self.dfItalic, + self.dfUnderline, self.dfStrikeOut, self.dfWeight, + self.dfCharSet, self.dfPixWidth, self.dfPixHeight, + self.dfPitchAndFamily, self.dfAvgWidth, self.dfMaxWidth, + self.dfFirstChar, self.dfLastChar, self.dfDefaultChar, + self.dfBreakChar, self.dfWidthBytes, self.dfDevice, self.dfFace, + self.dfBitsPointer, self.dfBitsOffset) = struct.unpack("= 600: + stemv = 120 + else: + stemv = 70 + file.write("/StemV %d\n" % stemv) + diff -Nru pyx-0.11.1/pyx/font/t1file.py pyx-0.12.1/pyx/font/t1file.py --- pyx-0.11.1/pyx/font/t1file.py 2011-05-18 09:09:35.000000000 +0000 +++ pyx-0.12.1/pyx/font/t1file.py 2012-10-17 15:31:57.000000000 +0000 @@ -72,7 +72,7 @@ class T1context: - def __init__(self, t1font): + def __init__(self, t1font, flex=True): """context for T1cmd evaluation""" self.t1font = t1font @@ -83,6 +83,7 @@ self.wy = None self.t1stack = [] self.psstack = [] + self.flex = flex ###################################################################### @@ -112,14 +113,14 @@ """update path instance applying trafo to the points""" raise NotImplementedError - def gathercalls(self, seacglyphs, subrs, othersubrs, context): + def gathercalls(self, seacglyphs, subrs, context): """gather dependancy information subrs is the "called-subrs" dictionary. gathercalls will insert the subr number as key having the value 1, i.e. subrs will become the numbers of used subrs. Similar seacglyphs will contain all glyphs in - composite characters (subrs and othersubrs for those glyphs will also - already be included) and othersubrs the othersubrs called. + composite characters (subrs for those glyphs will also + already be included). This method might will not properly update all information in the context (especially consuming values from the stack) and will also skip @@ -185,15 +186,15 @@ atrafo = atrafo * trafo.translate_pt(adx-sab, ady) context.t1font.updateglyphpath(aglyph, path, atrafo, context) - def gathercalls(self, seacglyphs, subrs, othersubrs, context): - bchar = context.t1stack.pop() + def gathercalls(self, seacglyphs, subrs, context): achar = context.t1stack.pop() + bchar = context.t1stack.pop() aglyph = adobestandardencoding[achar] bglyph = adobestandardencoding[bchar] seacglyphs.add(aglyph) seacglyphs.add(bglyph) - context.t1font.gatherglyphcalls(bglyph, seacglyphs, subrs, othersubrs, context) - context.t1font.gatherglyphcalls(aglyph, seacglyphs, subrs, othersubrs, context) + context.t1font.gatherglyphcalls(bglyph, seacglyphs, subrs, context) + context.t1font.gatherglyphcalls(aglyph, seacglyphs, subrs, context) T1seac = _T1seac() @@ -507,7 +508,7 @@ num1 = context.t1stack.pop() context.t1stack.append(divmod(num1, num2)[0]) - def gathercalls(self, seacglyphs, subrs, othersubrs, context): + def gathercalls(self, seacglyphs, subrs, context): num2 = context.t1stack.pop() num1 = context.t1stack.pop() context.t1stack.append(divmod(num1, num2)[0]) @@ -529,14 +530,34 @@ othersubrnumber = context.t1stack.pop() n = context.t1stack.pop() for i in range(n): - context.psstack.append(context.t1stack.pop()) + context.psstack.append(context.t1stack.pop(0)) + if othersubrnumber == 0: + flex_size, x, y = context.psstack[-3:] + if context.flex: + x1, y1, x2, y2, x3, y3 = context.psstack[2:8] + x1, y1 = trafo.apply_pt(x1, y1) + x2, y2 = trafo.apply_pt(x2, y2) + x3, y3 = trafo.apply_pt(x3, y3) + path.append(curveto_pt(x1, y1, x2, y2, x3, y3)) + x1, y1, x2, y2, x3, y3 = context.psstack[8:14] + x1, y1 = trafo.apply_pt(x1, y1) + x2, y2 = trafo.apply_pt(x2, y2) + x3, y3 = trafo.apply_pt(x3, y3) + path.append(curveto_pt(x1, y1, x2, y2, x3, y3)) + else: + path.append(lineto_pt(*trafo.apply_pt(x, y))) + context.psstack = [y, x] + elif othersubrnumber == 1: + pass + elif othersubrnumber == 2: + path.pathitems.pop() + context.psstack.append(context.x) + context.psstack.append(context.y) - def gathercalls(self, seacglyphs, subrs, othersubrs, context): + def gathercalls(self, seacglyphs, subrs, context): othersubrnumber = context.t1stack.pop() - othersubrs.add(othersubrnumber) n = context.t1stack.pop() - for i in range(n): - context.psstack.append(context.t1stack.pop()) + context.psstack.extend([context.t1stack.pop() for i in range(n)][::-1]) T1callothersubr = _T1callothersubr() @@ -553,10 +574,10 @@ subr = context.t1stack.pop() context.t1font.updatesubrpath(subr, path, trafo, context) - def gathercalls(self, seacglyphs, subrs, othersubrs, context): + def gathercalls(self, seacglyphs, subrs, context): subr = context.t1stack.pop() subrs.add(subr) - context.t1font.gathersubrcalls(subr, seacglyphs, subrs, othersubrs, context) + context.t1font.gathersubrcalls(subr, seacglyphs, subrs, context) T1callsubr = _T1callsubr() @@ -572,7 +593,7 @@ def updatepath(self, path, trafo, context): context.t1stack.append(context.psstack.pop()) - def gathercalls(self, seacglyphs, subrs, othersubrs, context): + def gathercalls(self, seacglyphs, subrs, context): context.t1stack.append(context.psstack.pop()) T1pop = _T1pop() @@ -601,11 +622,8 @@ return "setcurrentpoint" def updatepath(self, path, trafo, context): - x = context.t1stack.pop(0) - y = context.t1stack.pop(0) - path.append(moveto_pt(*trafo.apply_pt(x, y))) - context.x = x - context.y = y + context.x = context.t1stack.pop(0) + context.y = context.t1stack.pop(0) T1setcurrentpoint = _T1setcurrentpoint() @@ -863,37 +881,36 @@ def updateglyphpath(self, glyph, path, trafo, context): self.updatepath(self.getglyphcmds(glyph), path, trafo, context) - def gathercalls(self, cmds, seacglyphs, subrs, othersubrs, context): + def gathercalls(self, cmds, seacglyphs, subrs, context): for cmd in cmds: if isinstance(cmd, T1cmd): - cmd.gathercalls(seacglyphs, subrs, othersubrs, context) + cmd.gathercalls(seacglyphs, subrs, context) else: context.t1stack.append(cmd) - def gathersubrcalls(self, subr, seacglyphs, subrs, othersubrs, context): - self.gathercalls(self.getsubrcmds(subr), seacglyphs, subrs, othersubrs, context) + def gathersubrcalls(self, subr, seacglyphs, subrs, context): + self.gathercalls(self.getsubrcmds(subr), seacglyphs, subrs, context) - def gatherglyphcalls(self, glyph, seacglyphs, subrs, othersubrs, context): - self.gathercalls(self.getglyphcmds(glyph), seacglyphs, subrs, othersubrs, context) + def gatherglyphcalls(self, glyph, seacglyphs, subrs, context): + self.gathercalls(self.getglyphcmds(glyph), seacglyphs, subrs, context) - def getglyphpathwxwy_pt(self, glyph, size, convertcharcode=False): + def getglyphpath_pt(self, x_pt, y_pt, glyph, size_pt, convertcharcode=False, flex=True): + """return an object containing the PyX path, wx_pt and wy_pt for glyph named glyph""" if convertcharcode: if not self.encoding: self._encoding() glyph = self.encoding[glyph] - t = self.fontmatrix.scaled(size) - context = T1context(self) + t = self.fontmatrix.scaled(size_pt) + tpath = t.translated_pt(x_pt, y_pt) + context = T1context(self, flex=flex) p = path() - self.updateglyphpath(glyph, p, t, context) - wx, wy = t.apply_pt(context.wx, context.wy) - return p, wx, wy - - def getglyphpath(self, glyph, size, convertcharcode=False): - """return a PyX path for glyph named glyph""" - return self.getglyphpathwxwy_pt(glyph, size)[0] - - def getglyphwxwy_pt(self, glyph, size, convertcharcode=False): - return self.getglyphpathwxwy_pt(glyph, size)[1:] + self.updateglyphpath(glyph, p, tpath, context) + class glyphpath: + def __init__(self, p, wx_pt, wy_pt): + self.path = p + self.wx_pt = wx_pt + self.wy_pt = wy_pt + return glyphpath(p, *t.apply_pt(context.wx, context.wy)) def getdata2(self, subrs=None, glyphs=None): """makes a data2 string @@ -969,7 +986,6 @@ glyphs is a set of the glyph names. It might be modified *in place*! """ - # TODO: we could also strip othersubrs to those actually used if not self.encoding: self._encoding() for charcode in charcodes: @@ -978,9 +994,8 @@ # collect information about used glyphs and subrs seacglyphs = pycompat.set() subrs = pycompat.set() - othersubrs = pycompat.set() for glyph in glyphs: - self.gatherglyphcalls(glyph, seacglyphs, subrs, othersubrs, T1context(self)) + self.gatherglyphcalls(glyph, seacglyphs, subrs, T1context(self)) # while we have gathered all subrs for the seacglyphs alreadys, we # might have missed the glyphs themself (when they are not used stand-alone) glyphs.update(seacglyphs) @@ -994,7 +1009,7 @@ for char, glyph in enumerate(self.encoding): if glyph in glyphs: encodingstrings.append("dup %i /%s put\n" % (char, glyph)) - data1 = self.data1[:self.encodingstart] + "".join(encodingstrings) + self.data1[self.encodingend:] + data1 = self.data1[:self.encodingstart] + "\n" + "".join(encodingstrings) + self.data1[self.encodingend:] data1 = self.newlinepattern.subn("\n", data1)[0] data1 = self.uniqueidpattern.subn("", data1)[0] @@ -1043,23 +1058,34 @@ file.write("/CapHeight %f\n" % glyphinfo_h[5]) file.write("/StemV %f\n" % (glyphinfo_period[4]-glyphinfo_period[2])) - def getglyphinfo(self, glyph): + def getglyphinfo(self, glyph, flex=True): warnings.warn("We are about to extract font information for the Type 1 font '%s' from its pfb file. This is bad practice (and it's slow). You should use an afm file instead." % self.name) - context = T1context(self) + context = T1context(self, flex=flex) p = path() self.updateglyphpath(glyph, p, trafo.trafo(), context) bbox = p.bbox() return context.wx, context.wy, bbox.llx_pt, bbox.lly_pt, bbox.urx_pt, bbox.ury_pt - def outputPFA(self, file): + def outputPFA(self, file, remove_UniqueID_lookup=False): """output the T1file in PFA format""" - file.write(self.data1) + data1 = self.data1 + data3 = self.data3 + if remove_UniqueID_lookup: + m1 = re.search("""FontDirectory\s*/%(name)s\s+known{/%(name)s\s+findfont\s+dup\s*/UniqueID\s+known\s*{\s*dup\s* + /UniqueID\s+get\s+\d+\s+eq\s+exch\s*/FontType\s+get\s+1\s+eq\s+and\s*}\s*{\s*pop\s+false\s*}\s*ifelse\s* + {save\s+true\s*}\s*{\s*false\s*}\s*ifelse\s*}\s*{\s*false\s*}\s*ifelse""" % {"name": self.name}, + data1, re.VERBOSE) + m3 = re.search("\s*{restore}\s*if", data3) + if m1 and m3: + data1 = data1[:m1.start()] + data1[m1.end():] + data3 = data3[:m3.start()] + data3[m3.end():] + file.write(data1) data2eexechex = binascii.b2a_hex(self.getdata2eexec()) linelength = 64 for i in range((len(data2eexechex)-1)/linelength + 1): file.write(data2eexechex[i*linelength: i*linelength+linelength]) file.write("\n") - file.write(self.data3) + file.write(data3) def outputPFB(self, file): """output the T1file in PFB format""" @@ -1083,7 +1109,7 @@ def outputPS(self, file, writer): """output the PostScript code for the T1file to the file file""" - self.outputPFA(file) + self.outputPFA(file, remove_UniqueID_lookup=True) def outputPDF(self, file, writer): data2eexec = self.getdata2eexec() diff -Nru pyx-0.11.1/pyx/graph/__init__.py pyx-0.12.1/pyx/graph/__init__.py --- pyx-0.11.1/pyx/graph/__init__.py 2011-05-18 09:09:34.000000000 +0000 +++ pyx-0.12.1/pyx/graph/__init__.py 2012-10-17 15:31:57.000000000 +0000 @@ -1,7 +1,7 @@ # -*- encoding: utf-8 -*- # # -# Copyright (C) 2004-2005 André Wobst +# Copyright (C) 2004-2012 André Wobst # # This file is part of PyX (http://pyx.sourceforge.net/). # @@ -25,7 +25,7 @@ __import__(module, globals(), locals(), []) import graph -__allgraph__ = ["graphxy", "graphxyz"] +__allgraph__ = ["graphx", "graphxy", "graphxyz"] for importfromgraph in __allgraph__: locals()[importfromgraph] = getattr(graph, importfromgraph) diff -Nru pyx-0.11.1/pyx/graph/axis/axis.py pyx-0.12.1/pyx/graph/axis/axis.py --- pyx-0.11.1/pyx/graph/axis/axis.py 2011-05-18 09:09:33.000000000 +0000 +++ pyx-0.12.1/pyx/graph/axis/axis.py 2012-10-17 15:31:57.000000000 +0000 @@ -1,9 +1,9 @@ # -*- encoding: utf-8 -*- # # -# Copyright (C) 2002-2004 Jörg Lehmann +# Copyright (C) 2002-2012 Jörg Lehmann # Copyright (C) 2003-2011 Michael Schindler -# Copyright (C) 2002-2011 André Wobst +# Copyright (C) 2002-2012 André Wobst # # This file is part of PyX (http://pyx.sourceforge.net/). # @@ -123,12 +123,19 @@ else: raise RuntimeError("zero axis range%s" % errorname) + if self.divisor is not None: + divisor = self.divisor + rational_divisor = tick.rational(divisor) + else: + divisor = 1 + def layout(data): - self.adjustaxis(data, data.ticks, graphtexrunner, errorname) + if data.ticks: + self.adjustaxis(data, [float(data.ticks[0])*divisor, float(data.ticks[-1])*divisor], graphtexrunner, errorname) self.texter.labels(data.ticks) if self.divisor: for t in data.ticks: - t *= tick.rational(self.divisor) + t *= rational_divisor canvas = painter.axiscanvas(self.painter, graphtexrunner) if self.painter is not None: self.painter.paint(canvas, data, self, positioner) @@ -176,20 +183,20 @@ ticks = partfunction() if ticks is None: break - ticks = [t for t in tick.mergeticklists(self.manualticks, ticks, mergeequal=0) - if t.ticklevel is not None or t.labellevel is not None] + ticks = tick.mergeticklists(self.manualticks, ticks, mergeequal=0) if ticks: rate = rater.rateticks(self, ticks, self.density) - if self.reverse: - rate += rater.raterange(self.convert(data, ticks[0]) - - self.convert(data, ticks[-1]), 1) - else: - rate += rater.raterange(self.convert(data, ticks[-1]) - - self.convert(data, ticks[0]), 1) - if bestrate is None or rate < bestrate: - bestrate = rate - worse = 0 - variants.append(variant(data, rate=rate, ticks=ticks)) + if rate is not None: + if self.reverse: + rate += rater.raterange(self.convert(data, float(ticks[0])*divisor) - + self.convert(data, float(ticks[-1])*divisor), 1) + else: + rate += rater.raterange(self.convert(data, float(ticks[-1])*divisor) - + self.convert(data, float(ticks[0])*divisor), 1) + if bestrate is None or rate < bestrate: + bestrate = rate + worse = 0 + variants.append(variant(data, rate=rate, ticks=ticks)) if not variants: raise RuntimeError("no axis partitioning found%s" % errorname) @@ -403,7 +410,9 @@ subaxis.vmaxover = position / float(data.size) subaxis.setpositioner(subaxispositioner(positioner, subaxis)) subaxis.create() - canvas.insert(subaxis.canvas) + for layer, subcanvas in subaxis.canvas.layers.items(): + canvas.layer(layer).insert(subcanvas) + assert len(subaxis.canvas.layers) == len(subaxis.canvas.items) if canvas.extent_pt < subaxis.canvas.extent_pt: canvas.extent_pt = subaxis.canvas.extent_pt position += 0.5*self.dist @@ -419,7 +428,9 @@ subaxis = linkedaxis(subaxis, name) subaxis.setpositioner(subaxispositioner(positioner, data.subaxes[name])) subaxis.create() - canvas.insert(subaxis.canvas) + for layer, subcanvas in subaxis.canvas.layers.items(): + canvas.layer(layer).insert(subcanvas) + assert len(subaxis.canvas.layers) == len(subaxis.canvas.items) if canvas.extent_pt < subaxis.canvas.extent_pt: canvas.extent_pt = subaxis.canvas.extent_pt if linkpainter is not None: diff -Nru pyx-0.11.1/pyx/graph/axis/painter.py pyx-0.12.1/pyx/graph/axis/painter.py --- pyx-0.11.1/pyx/graph/axis/painter.py 2011-05-18 09:09:33.000000000 +0000 +++ pyx-0.12.1/pyx/graph/axis/painter.py 2012-10-17 15:31:57.000000000 +0000 @@ -1,9 +1,9 @@ # -*- encoding: utf-8 -*- # # -# Copyright (C) 2002-2004 Jörg Lehmann +# Copyright (C) 2002-2012 Jörg Lehmann # Copyright (C) 2003-2004 Michael Schindler -# Copyright (C) 2002-2011 André Wobst +# Copyright (C) 2002-2012 André Wobst # # This file is part of PyX (http://pyx.sourceforge.net/). # @@ -38,7 +38,7 @@ """initializes the instance - sets extent to zero - sets labels to an empty list""" - canvas._canvas.__init__(self) + canvas.canvas.__init__(self) self.extent_pt = 0 self.labels = [] if isinstance(painter, _text) and painter.texrunner: @@ -106,7 +106,7 @@ dy2 *= -1 dx2 *= -1 titleattrs.append(self.titledirection.trafo(dy2, -dx2)) - title = canvas.text_pt(x, y, axis.title, titleattrs) + title = canvas.layer("title").text_pt(x, y, axis.title, titleattrs) canvas.extent_pt += unit.topt(self.titledist) title.linealign_pt(canvas.extent_pt, -dx, -dy) canvas.extent_pt += title.extent_pt(dx, dy) @@ -224,7 +224,7 @@ y1 = t.temp_y_pt + t.temp_dy * innerticklength_pt x2 = t.temp_x_pt - t.temp_dx * outerticklength_pt y2 = t.temp_y_pt - t.temp_dy * outerticklength_pt - canvas.stroke(path.line_pt(x1, y1, x2, y2), tickattrs) + canvas.layer("ticks").stroke(path.line_pt(x1, y1, x2, y2), tickattrs) if outerticklength_pt > canvas.extent_pt: canvas.extent_pt = outerticklength_pt if -innerticklength_pt > canvas.extent_pt: @@ -232,9 +232,9 @@ if self.gridattrs is not None: gridattrs = attr.selectattrs(self.defaultgridattrs + self.gridattrs, t.ticklevel, maxticklevel) if gridattrs is not None: - canvas.stroke(axispos.vgridpath(t.temp_v), gridattrs) + canvas.layer("grid").stroke(axispos.vgridpath(t.temp_v), gridattrs) if t.labellevel is not None and self.labelattrs is not None: - canvas.insert(t.temp_labelbox) + canvas.layer("labels").insert(t.temp_labelbox) canvas.labels.append(t.temp_labelbox) extent_pt = t.temp_labelbox.extent_pt(t.temp_dx, t.temp_dy) + labeldist_pt if extent_pt > canvas.extent_pt: @@ -244,7 +244,7 @@ canvas.labels = None if self.basepathattrs is not None: - canvas.stroke(axispos.vbasepath(), self.defaultbasepathattrs + self.basepathattrs) + canvas.layer("baseline").stroke(axispos.vbasepath(), self.defaultbasepathattrs + self.basepathattrs) # for t in data.ticks: # del t.temp_v # we've inserted those temporary variables ... and do not care any longer about them @@ -331,7 +331,7 @@ if self.basepathattrs is not None: p = positioner.vbasepath() if p is not None: - canvas.stroke(p, self.defaultbasepathattrs + self.basepathattrs) + canvas.layer("baseline").stroke(p, self.defaultbasepathattrs + self.basepathattrs) if ( self.tickattrs is not None and (self.innerticklength is not None or self.outerticklength is not None) ): if self.innerticklength is not None: @@ -353,13 +353,13 @@ y1 = y + dy * innerticklength_pt x2 = x - dx * outerticklength_pt y2 = y - dy * outerticklength_pt - canvas.stroke(path.line_pt(x1, y1, x2, y2), self.defaulttickattrs + self.tickattrs) + canvas.layer("ticks").stroke(path.line_pt(x1, y1, x2, y2), self.defaulttickattrs + self.tickattrs) for (v, x, y, dx, dy), namebox in zip(namepos, nameboxes): newextent_pt = namebox.extent_pt(dx, dy) + labeldist_pt if canvas.extent_pt < newextent_pt: canvas.extent_pt = newextent_pt for namebox in nameboxes: - canvas.insert(namebox) + canvas.layer("labels").insert(namebox) _title.paint(self, canvas, data, axis, positioner) @@ -411,13 +411,13 @@ breakline = breakline.transformed(trafomodule.translate(*tocenter).rotated(self.breaklinesangle, *breakline.atbegin())) breakline1 = breakline.transformed(trafomodule.translate(*towidth)) breakline2 = breakline.transformed(trafomodule.translate(-towidth[0], -towidth[1])) - canvas.fill(path.path(path.moveto_pt(*breakline1.atbegin_pt()), - path.lineto_pt(*breakline1.atend_pt()), - path.lineto_pt(*breakline2.atend_pt()), - path.lineto_pt(*breakline2.atbegin_pt()), - path.closepath()), [color.gray.white]) - canvas.stroke(breakline1, self.defaultbreaklinesattrs + self.breaklinesattrs) - canvas.stroke(breakline2, self.defaultbreaklinesattrs + self.breaklinesattrs) + canvas.layer("baseline").fill(path.path(path.moveto_pt(*breakline1.atbegin_pt()), + path.lineto_pt(*breakline1.atend_pt()), + path.lineto_pt(*breakline2.atend_pt()), + path.lineto_pt(*breakline2.atbegin_pt()), + path.closepath()), [color.gray.white]) + canvas.layer("baseline").stroke(breakline1, self.defaultbreaklinesattrs + self.breaklinesattrs) + canvas.layer("baseline").stroke(breakline2, self.defaultbreaklinesattrs + self.breaklinesattrs) _title.paint(self, canvas, data, axis, axispos) diff -Nru pyx-0.11.1/pyx/graph/axis/rater.py pyx-0.12.1/pyx/graph/axis/rater.py --- pyx-0.11.1/pyx/graph/axis/rater.py 2011-05-18 09:09:33.000000000 +0000 +++ pyx-0.12.1/pyx/graph/axis/rater.py 2012-10-17 15:31:57.000000000 +0000 @@ -3,7 +3,7 @@ # # Copyright (C) 2002-2004 Jörg Lehmann # Copyright (C) 2003-2004 Michael Schindler -# Copyright (C) 2002-2006 André Wobst +# Copyright (C) 2002-2012 André Wobst # # This file is part of PyX (http://pyx.sourceforge.net/). # @@ -166,6 +166,8 @@ - within the rating, all ticks with a higher level are considered as ticks for a given level""" maxticklevel, maxlabellevel = tick.maxlevels(ticks) + if not maxticklevel and not maxlabellevel: + return None numticks = [0]*maxticklevel numlabels = [0]*maxlabellevel for t in ticks: diff -Nru pyx-0.11.1/pyx/graph/data.py pyx-0.12.1/pyx/graph/data.py --- pyx-0.11.1/pyx/graph/data.py 2011-05-18 09:09:34.000000000 +0000 +++ pyx-0.12.1/pyx/graph/data.py 2012-10-17 15:31:57.000000000 +0000 @@ -3,7 +3,7 @@ # # Copyright (C) 2002-2004 Jörg Lehmann # Copyright (C) 2003-2004 Michael Schindler -# Copyright (C) 2002-2006 André Wobst +# Copyright (C) 2002-2012 André Wobst # # This file is part of PyX (http://pyx.sourceforge.net/). # @@ -22,9 +22,9 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA import math, re, ConfigParser, struct, warnings -from pyx import text -from pyx.style import linestyle -from pyx.graph import style +from pyx import text, pycompat +import style +builtinlist = list def splitatvalue(value, *splitpoints): @@ -66,13 +66,13 @@ class _data: """graph data interface - Graph data consists in columns, where each column might be identified by a + Graph data consists of columns, where each column might be identified by a string or an integer. Each row in the resulting table refers to a data point. All methods except for the constructor should consider self and its attributes to be readonly, since the data instance might be shared between - several graphs simultaniously. + several graphs simultaneously. The instance variable columns is a dictionary mapping column names to the data of the column (i.e. to a list). Only static columns (known at @@ -85,15 +85,19 @@ and stated in the columnnames dictionary. The instance variable title and defaultstyles contain the data title and - the default styles (a list of styles), respectively. + the default styles (a list of styles), respectively. If defaultstyles is None, + the data cannot be plotted without user provided styles. """ - def dynamiccolumns(self, graph): + def dynamiccolumns(self, graph, axisnames): """create and return dynamic columns data Returns dynamic data matching the given axes (the axes range and other data might be used). The return value is a dictionary similar to the - columns instance variable. + columns instance variable. However, the static and dynamic data does + not need to be correlated in any way, i.e. the number of data points in + self.columns might differ from the number of data points represented by + the return value of the dynamiccolumns method. """ return {} @@ -523,10 +527,10 @@ self.columns = {} self.columnnames = [self.xname, self.yname] - def dynamiccolumns(self, graph): + def dynamiccolumns(self, graph, axisnames): dynamiccolumns = {self.xname: [], self.yname: []} - xaxis = graph.axes[self.xname] + xaxis = graph.axes[axisnames.get(self.xname, self.xname)] from pyx.graph.axis import logarithmic logaxis = isinstance(xaxis.axis, logarithmic) if self.min is not None: @@ -591,3 +595,64 @@ def __init__(self, f, min, max, **kwargs): paramfunction.__init__(self, "t", min, max, "x, y = f(t)", context={"f": f}, **kwargs) + + +class _nodefaultstyles: + pass + + +class join(_data): + "creates a new data set by joining from a list of data, it does however *not* combine points, but fills data with None if necessary" + + def merge_lists(self, lists): + "merges list items w/o duplications, resulting order is arbitraty" + result = pycompat.set() + for l in lists: + result.update(pycompat.set(l)) + return builtinlist(result) + + def merge_dicts(self, dicts): + """merge dicts containing lists as values (with equal number of items + per list in each dict), missing data is padded by None""" + keys = self.merge_lists([d.keys() for d in dicts]) + empties = [] + for d in dicts: + if len(d.keys()) == len(keys): + empties.append(None) # won't be needed later on + else: + values = d.values() + if len(values): + empties.append([None]*len(values[0])) + else: + # has no data at all -> do not add anything + empties.append([]) + result = {} + for key in keys: + result[key] = [] + for d, e in zip(dicts, empties): + result[key].extend(d.get(key, e)) + return result + + def __init__(self, data, title=_notitle, defaultstyles=_nodefaultstyles): + """takes a list of data, a title (if it should not be autoconstructed) + and a defaultstyles list if there is no common defaultstyles setting + for in the provided data""" + assert len(data) + self.data = data + self.columnnames = self.merge_lists([d.columnnames for d in data]) + self.columns = self.merge_dicts([d.columns for d in data]) + if title is _notitle: + self.title = " + ".join([d.title for d in data]) + else: + self.title = title + if defaultstyles is _nodefaultstyles: + self.defaultstyles = data[0].defaultstyles + for d in data[1:]: + if d.defaultstyles is not self.defaultstyles: + self.defaultstyles = None + break + else: + self.defaultstyles = defaultstyles + + def dynamiccolumns(self, graph, axisnames): + return self.merge_dicts([d.dynamiccolumns(graph, axisnames) for d in self.data]) diff -Nru pyx-0.11.1/pyx/graph/graph.py pyx-0.12.1/pyx/graph/graph.py --- pyx-0.11.1/pyx/graph/graph.py 2011-05-18 09:09:34.000000000 +0000 +++ pyx-0.12.1/pyx/graph/graph.py 2012-10-17 15:31:57.000000000 +0000 @@ -1,9 +1,9 @@ # -*- encoding: utf-8 -*- # # -# Copyright (C) 2002-2004 Jörg Lehmann +# Copyright (C) 2002-2012 Jörg Lehmann # Copyright (C) 2003-2004 Michael Schindler -# Copyright (C) 2002-2011 André Wobst +# Copyright (C) 2002-2012 André Wobst # # This file is part of PyX (http://pyx.sourceforge.net/). # @@ -23,14 +23,33 @@ import math, re, string, warnings -from pyx import canvas, path, trafo, unit -from pyx.graph import style +from pyx import canvas, path, pycompat, trafo, unit from pyx.graph.axis import axis, positioner goldenmean = 0.5 * (math.sqrt(5) + 1) +# The following two methods are used to register and get a default provider +# for keys. A key is a variable name in sharedata. A provider is a style +# which creates variables in sharedata. + +_defaultprovider = {} + +def registerdefaultprovider(style, keys): + """sets a style as a default creator for sharedata variables 'keys'""" + for key in keys: + assert key in style.providesdata, "key not provided by style" + # we might allow for overwriting the defaults, i.e. the following is not checked: + # assert key in _defaultprovider.keys(), "default provider already registered for key" + _defaultprovider[key] = style + +def getdefaultprovider(key): + """returns a style, which acts as a default creator for the + sharedata variable 'key'""" + return _defaultprovider[key] + + class styledata: """style data storage class @@ -57,7 +76,7 @@ for s in styles: for n in s.needsdata: if n not in provided: - defaultprovider = style.getdefaultprovider(n) + defaultprovider = getdefaultprovider(n) addstyles.append(defaultprovider) provided.extend(defaultprovider.providesdata) provided.extend(s.providesdata) @@ -65,12 +84,13 @@ self.styles = styles self.sharedata = styledata() + self.dataaxisnames = {} self.privatedatalist = [styledata() for s in self.styles] # perform setcolumns to all styles - self.usedcolumnnames = [] + self.usedcolumnnames = pycompat.set() for privatedata, s in zip(self.privatedatalist, self.styles): - self.usedcolumnnames.extend(s.columnnames(privatedata, self.sharedata, graph, self.data.columnnames)) + self.usedcolumnnames.update(pycompat.set(s.columnnames(privatedata, self.sharedata, graph, self.data.columnnames, self.dataaxisnames))) def selectstyles(self, graph, selectindex, selecttotal): for privatedata, style in zip(self.privatedatalist, self.styles): @@ -79,31 +99,39 @@ def adjustaxesstatic(self, graph): for columnname, data in self.data.columns.items(): for privatedata, style in zip(self.privatedatalist, self.styles): - style.adjustaxis(privatedata, self.sharedata, graph, columnname, data) + style.adjustaxis(privatedata, self.sharedata, graph, self, columnname, data) def makedynamicdata(self, graph): - self.dynamiccolumns = self.data.dynamiccolumns(graph) + self.dynamiccolumns = self.data.dynamiccolumns(graph, self.dataaxisnames) def adjustaxesdynamic(self, graph): for columnname, data in self.dynamiccolumns.items(): for privatedata, style in zip(self.privatedatalist, self.styles): - style.adjustaxis(privatedata, self.sharedata, graph, columnname, data) + style.adjustaxis(privatedata, self.sharedata, graph, self, columnname, data) def draw(self, graph): for privatedata, style in zip(self.privatedatalist, self.styles): style.initdrawpoints(privatedata, self.sharedata, graph) - point = {} - useitems = [] - for columnname in self.usedcolumnnames: - try: - useitems.append((columnname, self.dynamiccolumns[columnname])) - except KeyError: - useitems.append((columnname, self.data.columns[columnname])) - if not useitems: - raise ValueError("cannot draw empty data") - for i in xrange(len(useitems[0][1])): - for columnname, data in useitems: - point[columnname] = data[i] + + point = dict([(columnname, None) for columnname in self.usedcolumnnames]) + # fill point with (static) column data first + columns = self.data.columns.keys() + for values in zip(*self.data.columns.values()): + for column, value in zip(columns, values): + point[column] = value + for privatedata, style in zip(self.privatedatalist, self.styles): + style.drawpoint(privatedata, self.sharedata, graph, point) + + point = dict([(columnname, None) for columnname in self.usedcolumnnames]) + # insert an empty point + if self.data.columns and self.dynamiccolumns: + for privatedata, style in zip(self.privatedatalist, self.styles): + style.drawpoint(privatedata, self.sharedata, graph, point) + # fill point with dynamic column data + columns = self.dynamiccolumns.keys() + for values in zip(*self.dynamiccolumns.values()): + for key, value in zip(columns, values): + point[key] = value for privatedata, style in zip(self.privatedatalist, self.styles): style.drawpoint(privatedata, self.sharedata, graph, point) for privatedata, style in zip(self.privatedatalist, self.styles): @@ -115,6 +143,8 @@ def __getattr__(self, attr): # read only access to the styles privatedata + # this is just a convenience method + # use case: access the path of a the line style stylesdata = [getattr(styledata, attr) for styledata in self.privatedatalist if hasattr(styledata, attr)] @@ -129,6 +159,8 @@ def __init__(self): canvas.canvas.__init__(self) + for name in ["background", "filldata", "axes.grid", "axis.baseline", "axis.ticks", "axis.labels", "axis.title", "data", "key"]: + self.layer(name) self.axes = {} self.plotitems = [] self.keyitems = [] @@ -149,6 +181,7 @@ self.finish() return canvas.canvas.bbox(self) + def registerPS(self, registry): self.finish() canvas.canvas.registerPS(self, registry) @@ -283,7 +316,7 @@ class graphxy(graph): def __init__(self, xpos=0, ypos=0, width=None, height=None, ratio=goldenmean, - key=None, backgroundattrs=None, axesdist=0.8*unit.v_cm, + key=None, backgroundattrs=None, axesdist=0.8*unit.v_cm, flipped=False, xaxisat=None, yaxisat=None, **axes): graph.__init__(self) @@ -296,6 +329,7 @@ self.key = key self.backgroundattrs = backgroundattrs self.axesdist_pt = unit.topt(axesdist) + self.flipped = flipped self.width = width self.height = height @@ -360,28 +394,57 @@ self.axesnames[1].append(axisname) aaxis.setcreatecall(self.doaxiscreate, axisname) + self.axespositioners = dict(x=positioner.lineaxispos_pt(self.xpos_pt, self.ypos_pt, + self.xpos_pt + self.width_pt, self.ypos_pt, + (0, 1), self.xvgridpath), + x2=positioner.lineaxispos_pt(self.xpos_pt, self.ypos_pt + self.height_pt, + self.xpos_pt + self.width_pt, self.ypos_pt + self.height_pt, + (0, -1), self.xvgridpath), + y=positioner.lineaxispos_pt(self.xpos_pt, self.ypos_pt, + self.xpos_pt, self.ypos_pt + self.height_pt, + (1, 0), self.yvgridpath), + y2=positioner.lineaxispos_pt(self.xpos_pt + self.width_pt, self.ypos_pt, + self.xpos_pt + self.width_pt, self.ypos_pt + self.height_pt, + (-1, 0), self.yvgridpath)) + if self.flipped: + self.axespositioners = dict(x=self.axespositioners["y2"], + y2=self.axespositioners["x2"], + y=self.axespositioners["x"], + x2=self.axespositioners["y"]) def pos_pt(self, x, y, xaxis=None, yaxis=None): if xaxis is None: xaxis = self.axes["x"] if yaxis is None: yaxis = self.axes["y"] - return (self.xpos_pt + xaxis.convert(x)*self.width_pt, - self.ypos_pt + yaxis.convert(y)*self.height_pt) + vx = xaxis.convert(x) + vy = yaxis.convert(y) + if self.flipped: + vx, vy = vy, vx + return (self.xpos_pt + vx*self.width_pt, + self.ypos_pt + vy*self.height_pt) def pos(self, x, y, xaxis=None, yaxis=None): if xaxis is None: xaxis = self.axes["x"] if yaxis is None: yaxis = self.axes["y"] - return (self.xpos + xaxis.convert(x)*self.width, - self.ypos + yaxis.convert(y)*self.height) + vx = xaxis.convert(x) + vy = yaxis.convert(y) + if self.flipped: + vx, vy = vy, vx + return (self.xpos + vx*self.width, + self.ypos + vy*self.height) def vpos_pt(self, vx, vy): + if self.flipped: + vx, vy = vy, vx return (self.xpos_pt + vx*self.width_pt, self.ypos_pt + vy*self.height_pt) def vpos(self, vx, vy): + if self.flipped: + vx, vy = vy, vx return (self.xpos + vx*self.width, self.ypos + vy*self.height) @@ -393,6 +456,9 @@ def vgeodesic(self, vx1, vy1, vx2, vy2): """returns a geodesic path between two points in graph coordinates""" + if self.flipped: + vx1, vy1 = vy1, vx1 + vx2, vy2 = vy2, vx2 return path.line_pt(self.xpos_pt + vx1*self.width_pt, self.ypos_pt + vy1*self.height_pt, self.xpos_pt + vx2*self.width_pt, @@ -400,12 +466,18 @@ def vgeodesic_el(self, vx1, vy1, vx2, vy2): """returns a geodesic path element between two points in graph coordinates""" + if self.flipped: + vx1, vy1 = vy1, vx1 + vx2, vy2 = vy2, vx2 return path.lineto_pt(self.xpos_pt + vx2*self.width_pt, self.ypos_pt + vy2*self.height_pt) def vcap_pt(self, coordinate, length_pt, vx, vy): """returns an error cap path for a given coordinate, lengths and point in graph coordinates""" + if self.flipped: + coordinate = 1-coordinate + vx, vy = vy, vx if coordinate == 0: return path.line_pt(self.xpos_pt + vx*self.width_pt - 0.5*length_pt, self.ypos_pt + vy*self.height_pt, @@ -427,39 +499,52 @@ return path.line_pt(self.xpos_pt, self.ypos_pt + vy*self.height_pt, self.xpos_pt + self.width_pt, self.ypos_pt + vy*self.height_pt) - def axistrafo(self, axis, t): - c = canvas.canvas([t]) - c.insert(axis.canvas) - axis.canvas = c + def autokeygraphattrs(self): + return dict(direction="vertical", length=self.height) + + def autokeygraphtrafo(self, keygraph): + dependsonaxisnumber = None + if self.flipped: + dependsonaxisname = "x" + else: + dependsonaxisname = "y" + for axisname in self.axes: + if axisname[0] == dependsonaxisname: + if len(axisname) == 1: + axisname += "1" + axisnumber = int(axisname[1:]) + if not (axisnumber % 2) and not self.flipped or (axisnumber % 2) and self.flipped: + if dependsonaxisnumber is None or dependsonaxisnumber < axisnumber: + dependsonaxisnumber = axisnumber + if dependsonaxisnumber is None: + x_pt = self.xpos_pt + self.width_pt + else: + if dependsonaxisnumber > 1: + dependsonaxisname += str(dependsonaxisnumber) + self.doaxiscreate(dependsonaxisname) + x_pt = self.axes[dependsonaxisname].positioner.x1_pt + self.axes[dependsonaxisname].canvas.extent_pt + x_pt += self.axesdist_pt + return trafo.translate_pt(x_pt, self.ypos_pt) def axisatv(self, axis, v): if axis.positioner.fixtickdirection[0]: # it is a y-axis - self.axistrafo(axis, trafo.translate_pt(self.xpos_pt + v*self.width_pt - axis.positioner.x1_pt, 0)) + t = trafo.translate_pt(self.xpos_pt + v*self.width_pt - axis.positioner.x1_pt, 0) else: # it is an x-axis - self.axistrafo(axis, trafo.translate_pt(0, self.ypos_pt + v*self.height_pt - axis.positioner.y1_pt)) + t = trafo.translate_pt(0, self.ypos_pt + v*self.height_pt - axis.positioner.y1_pt) + c = canvas.canvas() + for layer, subcanvas in axis.canvas.layers.items(): + c.layer(layer).insert(subcanvas, [t]) + assert len(axis.canvas.layers) == len(axis.canvas.items), str(axis.canvas.items) + axis.canvas = c def doaxispositioner(self, axisname): if self.did(self.doaxispositioner, axisname): return self.doranges() - if axisname == "x": - self.axes["x"].setpositioner(positioner.lineaxispos_pt(self.xpos_pt, self.ypos_pt, - self.xpos_pt + self.width_pt, self.ypos_pt, - (0, 1), self.xvgridpath)) - elif axisname == "x2": - self.axes["x2"].setpositioner(positioner.lineaxispos_pt(self.xpos_pt, self.ypos_pt + self.height_pt, - self.xpos_pt + self.width_pt, self.ypos_pt + self.height_pt, - (0, -1), self.xvgridpath)) - elif axisname == "y": - self.axes["y"].setpositioner(positioner.lineaxispos_pt(self.xpos_pt, self.ypos_pt, - self.xpos_pt, self.ypos_pt + self.height_pt, - (1, 0), self.yvgridpath)) - elif axisname == "y2": - self.axes["y2"].setpositioner(positioner.lineaxispos_pt(self.xpos_pt + self.width_pt, self.ypos_pt, - self.xpos_pt + self.width_pt, self.ypos_pt + self.height_pt, - (-1, 0), self.yvgridpath)) + if axisname in ["x", "x2", "y", "y2"]: + self.axes[axisname].setpositioner(self.axespositioners[axisname]) else: if axisname[1:] == "3": dependsonaxisname = axisname[0] @@ -467,7 +552,9 @@ dependsonaxisname = "%s%d" % (axisname[0], int(axisname[1:]) - 2) self.doaxiscreate(dependsonaxisname) sign = 2*(int(axisname[1:]) % 2) - 1 - if axisname[0] == "x": + if axisname[0] == "x" and self.flipped: + sign = -sign + if axisname[0] == "x" and not self.flipped or axisname[0] == "y" and self.flipped: y_pt = self.axes[dependsonaxisname].positioner.y1_pt - sign * (self.axes[dependsonaxisname].canvas.extent_pt + self.axesdist_pt) self.axes[axisname].setpositioner(positioner.lineaxispos_pt(self.xpos_pt, y_pt, self.xpos_pt + self.width_pt, y_pt, @@ -492,8 +579,8 @@ if self.did(self.dobackground): return if self.backgroundattrs is not None: - self.draw(path.rect_pt(self.xpos_pt, self.ypos_pt, self.width_pt, self.height_pt), - self.backgroundattrs) + self.layer("background").draw(path.rect_pt(self.xpos_pt, self.ypos_pt, self.width_pt, self.height_pt), + self.backgroundattrs) def doaxes(self): if self.did(self.doaxes): @@ -501,7 +588,9 @@ self.dolayout() self.dobackground() for axis in self.axes.values(): - self.insert(axis.canvas) + for layer, canvas in axis.canvas.layers.items(): + self.layer("axes.%s" % layer).insert(canvas) + assert len(axis.canvas.layers) == len(axis.canvas.items), str(axis.canvas.items) def dokey(self): if self.did(self.dokey): @@ -523,10 +612,65 @@ y = parentchildalign(self.ypos_pt, self.ypos_pt+self.height_pt, bbox.lly_pt, bbox.ury_pt, self.key.vpos, unit.topt(self.key.vdist), self.key.vinside) - self.insert(c, [trafo.translate_pt(x, y)]) + self.layer("key").insert(c, [trafo.translate_pt(x, y)]) + + + +class graphx(graphxy): + + def __init__(self, xpos=0, ypos=0, length=None, size=0.5*unit.v_cm, direction="vertical", + key=None, backgroundattrs=None, axesdist=0.8*unit.v_cm, **axes): + for name in axes: + if not name.startswith("x"): + raise ValueError("Only x axes are allowed") + self.direction = direction + if self.direction == "vertical": + kwargsxy = dict(width=size, height=length, flipped=True) + elif self.direction == "horizontal": + kwargsxy = dict(width=length, height=size) + else: + raise ValueError("vertical or horizontal direction required") + kwargsxy.update(**axes) + + graphxy.__init__(self, xpos=xpos, ypos=ypos, ratio=None, key=key, y=axis.lin(min=0, max=1, parter=None), + backgroundattrs=backgroundattrs, axesdist=axesdist, **kwargsxy) + + def pos_pt(self, x, xaxis=None): + return graphxy.pos_pt(self, x, 0.5, xaxis) + def pos(self, x, xaxis=None): + return graphxy.pos(self, x, 0.5, xaxis) -class graphxyz(graphxy): + def vpos_pt(self, vx): + return graphxy.vpos_pt(self, vx, 0.5) + + def vpos(self, vx): + return graphxy.vpos(self, vx, 0.5) + + def vgeodesic(self, vx1, vx2): + return graphxy.vgeodesic(self, vx1, 0.5, vx2, 0.5) + + def vgeodesic_el(self, vx1, vy1, vx2, vy2): + return graphxy.vgeodesic_el(self, vx1, 0.5, vx2, 0.5) + + def vcap_pt(self, coordinate, length_pt, vx): + if coordinate == 0: + return graphxy.vcap_pt(self, coordinate, length_pt, vx, 0.5) + else: + raise ValueError("direction invalid") + + def xvgridpath(self, vx): + return graphxy.xvgridpath(self, vx) + + def yvgridpath(self, vy): + raise Exception("This method does not exist on a one dimensional graph.") + + def axisatv(self, axis, v): + raise Exception("This method does not exist on a one dimensional graph.") + + + +class graphxyz(graph): class central: @@ -606,9 +750,12 @@ def __init__(self, xpos=0, ypos=0, size=None, xscale=1, yscale=1, zscale=1/goldenmean, - projector=central(10, -30, 30), key=None, + projector=central(10, -30, 30), axesdist=0.8*unit.v_cm, key=None, **axes): graph.__init__(self) + self.layer("hiddenaxes", below=self.layer("filldata")) + for name in ["hiddenaxes.grid", "hiddenaxes.baseline", "hiddenaxes.ticks", "hiddenaxes.labels", "hiddenaxes.title"]: + self.layer(name) self.xpos = xpos self.ypos = ypos @@ -620,12 +767,25 @@ self.yscale = yscale self.zscale = zscale self.projector = projector + self.axesdist_pt = unit.topt(axesdist) self.key = key self.xorder = projector.zindex(0, -1, 0) > projector.zindex(0, 1, 0) and 1 or 0 self.yorder = projector.zindex(-1, 0, 0) > projector.zindex(1, 0, 0) and 1 or 0 self.zindexscale = math.sqrt(xscale*xscale+yscale*yscale+zscale*zscale) + # the pXYshow attributes are booleans stating whether plane perpendicular to axis X + # at the virtual graph coordinate Y will be hidden by data or not. An axis is considered + # to be visible if one of the two planes it is part of is visible. Other axes are drawn + # in the hiddenaxes layer (i.e. layer group). + # TODO: Tick and grid visibility is treated like the axis visibility at the moment. + self.pz0show = self.vangle(0, 0, 0, 1, 0, 0, 1, 1, 0) > 0 + self.pz1show = self.vangle(0, 0, 1, 0, 1, 1, 1, 1, 1) > 0 + self.py0show = self.vangle(0, 0, 0, 0, 0, 1, 1, 0, 1) > 0 + self.py1show = self.vangle(0, 1, 0, 1, 1, 0, 1, 1, 1) > 0 + self.px0show = self.vangle(0, 0, 0, 0, 1, 0, 0, 1, 1) > 0 + self.px1show = self.vangle(1, 0, 0, 1, 0, 1, 1, 1, 1) > 0 + for axisname, aaxis in axes.items(): if aaxis is not None: if not isinstance(aaxis, axis.linkedaxis): @@ -641,6 +801,8 @@ self.axes[okey] = axis.linkedaxis(self.axes[axisname], okey) else: self.axes[axisname] = axis.linkedaxis(self.axes[okey], axisname) + elif not axes.has_key(okey): + self.axes[okey] = axis.linkedaxis(self.axes[axisname], okey) if not axes.has_key("z"): self.axes["z"] = axis.anchoredaxis(axis.linear(), self.texrunner, "z") @@ -809,58 +971,103 @@ path.lineto_pt(*self.vpos_pt(0, 1, vz)), path.closepath()) + def autokeygraphattrs(self): + return dict(direction="vertical", length=self.size) + + def autokeygraphtrafo(self, keygraph): + self.doaxes() + x_pt = self.layer("axes").bbox().right_pt() + self.axesdist_pt + y_pt = 0.5*(self.layer("axes").bbox().top_pt() + self.layer("axes").bbox().bottom_pt() - self.size_pt) + return trafo.translate_pt(x_pt, y_pt) + def doaxispositioner(self, axisname): if self.did(self.doaxispositioner, axisname): return self.doranges() if axisname == "x": - self.axes["x"].setpositioner(positioner.flexlineaxispos_pt(lambda vx: self.vpos_pt(vx, self.xorder, 0), - lambda vx: self.vtickdirection(vx, self.xorder, 0, vx, 1-self.xorder, 0), - self.xvgridpath)) + self.axes[axisname].setpositioner(positioner.flexlineaxispos_pt(lambda vx: self.vpos_pt(vx, self.xorder, 0), + lambda vx: self.vtickdirection(vx, self.xorder, 0, vx, 1-self.xorder, 0), + self.xvgridpath)) + if self.xorder: + self.axes[axisname].hidden = not self.py1show and not self.pz0show + else: + self.axes[axisname].hidden = not self.py0show and not self.pz0show elif axisname == "x2": - self.axes["x2"].setpositioner(positioner.flexlineaxispos_pt(lambda vx: self.vpos_pt(vx, 1-self.xorder, 0), - lambda vx: self.vtickdirection(vx, 1-self.xorder, 0, vx, self.xorder, 0), - self.xvgridpath)) + self.axes[axisname].setpositioner(positioner.flexlineaxispos_pt(lambda vx: self.vpos_pt(vx, 1-self.xorder, 0), + lambda vx: self.vtickdirection(vx, 1-self.xorder, 0, vx, self.xorder, 0), + self.xvgridpath)) + if self.xorder: + self.axes[axisname].hidden = not self.py0show and not self.pz0show + else: + self.axes[axisname].hidden = not self.py1show and not self.pz0show elif axisname == "x3": - self.axes["x3"].setpositioner(positioner.flexlineaxispos_pt(lambda vx: self.vpos_pt(vx, self.xorder, 1), - lambda vx: self.vtickdirection(vx, self.xorder, 1, vx, 1-self.xorder, 1), - self.xvgridpath)) + self.axes[axisname].setpositioner(positioner.flexlineaxispos_pt(lambda vx: self.vpos_pt(vx, self.xorder, 1), + lambda vx: self.vtickdirection(vx, self.xorder, 1, vx, 1-self.xorder, 1), + self.xvgridpath)) + if self.xorder: + self.axes[axisname].hidden = not self.py1show and not self.pz1show + else: + self.axes[axisname].hidden = not self.py0show and not self.pz1show elif axisname == "x4": - self.axes["x4"].setpositioner(positioner.flexlineaxispos_pt(lambda vx: self.vpos_pt(vx, 1-self.xorder, 1), - lambda vx: self.vtickdirection(vx, 1-self.xorder, 1, vx, self.xorder, 1), - self.xvgridpath)) + self.axes[axisname].setpositioner(positioner.flexlineaxispos_pt(lambda vx: self.vpos_pt(vx, 1-self.xorder, 1), + lambda vx: self.vtickdirection(vx, 1-self.xorder, 1, vx, self.xorder, 1), + self.xvgridpath)) + if self.xorder: + self.axes[axisname].hidden = not self.py0show and not self.pz1show + else: + self.axes[axisname].hidden = not self.py1show and not self.pz1show elif axisname == "y": - self.axes["y"].setpositioner(positioner.flexlineaxispos_pt(lambda vy: self.vpos_pt(self.yorder, vy, 0), - lambda vy: self.vtickdirection(self.yorder, vy, 0, 1-self.yorder, vy, 0), - self.yvgridpath)) + self.axes[axisname].setpositioner(positioner.flexlineaxispos_pt(lambda vy: self.vpos_pt(self.yorder, vy, 0), + lambda vy: self.vtickdirection(self.yorder, vy, 0, 1-self.yorder, vy, 0), + self.yvgridpath)) + if self.yorder: + self.axes[axisname].hidden = not self.px1show and not self.pz0show + else: + self.axes[axisname].hidden = not self.px0show and not self.pz0show elif axisname == "y2": - self.axes["y2"].setpositioner(positioner.flexlineaxispos_pt(lambda vy: self.vpos_pt(1-self.yorder, vy, 0), - lambda vy: self.vtickdirection(1-self.yorder, vy, 0, self.yorder, vy, 0), - self.yvgridpath)) + self.axes[axisname].setpositioner(positioner.flexlineaxispos_pt(lambda vy: self.vpos_pt(1-self.yorder, vy, 0), + lambda vy: self.vtickdirection(1-self.yorder, vy, 0, self.yorder, vy, 0), + self.yvgridpath)) + if self.yorder: + self.axes[axisname].hidden = not self.px0show and not self.pz0show + else: + self.axes[axisname].hidden = not self.px1show and not self.pz0show elif axisname == "y3": - self.axes["y3"].setpositioner(positioner.flexlineaxispos_pt(lambda vy: self.vpos_pt(self.yorder, vy, 1), - lambda vy: self.vtickdirection(self.yorder, vy, 1, 1-self.yorder, vy, 1), - self.yvgridpath)) + self.axes[axisname].setpositioner(positioner.flexlineaxispos_pt(lambda vy: self.vpos_pt(self.yorder, vy, 1), + lambda vy: self.vtickdirection(self.yorder, vy, 1, 1-self.yorder, vy, 1), + self.yvgridpath)) + if self.yorder: + self.axes[axisname].hidden = not self.px1show and not self.pz1show + else: + self.axes[axisname].hidden = not self.px0show and not self.pz1show elif axisname == "y4": - self.axes["y4"].setpositioner(positioner.flexlineaxispos_pt(lambda vy: self.vpos_pt(1-self.yorder, vy, 1), - lambda vy: self.vtickdirection(1-self.yorder, vy, 1, self.yorder, vy, 1), - self.yvgridpath)) + self.axes[axisname].setpositioner(positioner.flexlineaxispos_pt(lambda vy: self.vpos_pt(1-self.yorder, vy, 1), + lambda vy: self.vtickdirection(1-self.yorder, vy, 1, self.yorder, vy, 1), + self.yvgridpath)) + if self.yorder: + self.axes[axisname].hidden = not self.px0show and not self.pz1show + else: + self.axes[axisname].hidden = not self.px1show and not self.pz1show elif axisname == "z": - self.axes["z"].setpositioner(positioner.flexlineaxispos_pt(lambda vz: self.vpos_pt(0, 0, vz), - lambda vz: self.vtickdirection(0, 0, vz, 1, 1, vz), - self.zvgridpath)) + self.axes[axisname].setpositioner(positioner.flexlineaxispos_pt(lambda vz: self.vpos_pt(0, 0, vz), + lambda vz: self.vtickdirection(0, 0, vz, 1, 1, vz), + self.zvgridpath)) + self.axes[axisname].hidden = not self.px0show and not self.py0show elif axisname == "z2": - self.axes["z2"].setpositioner(positioner.flexlineaxispos_pt(lambda vz: self.vpos_pt(1, 0, vz), - lambda vz: self.vtickdirection(1, 0, vz, 0, 1, vz), - self.zvgridpath)) + self.axes[axisname].setpositioner(positioner.flexlineaxispos_pt(lambda vz: self.vpos_pt(1, 0, vz), + lambda vz: self.vtickdirection(1, 0, vz, 0, 1, vz), + self.zvgridpath)) + self.axes[axisname].hidden = not self.px1show and not self.py0show elif axisname == "z3": - self.axes["z3"].setpositioner(positioner.flexlineaxispos_pt(lambda vz: self.vpos_pt(0, 1, vz), - lambda vz: self.vtickdirection(0, 1, vz, 1, 0, vz), - self.zvgridpath)) + self.axes[axisname].setpositioner(positioner.flexlineaxispos_pt(lambda vz: self.vpos_pt(0, 1, vz), + lambda vz: self.vtickdirection(0, 1, vz, 1, 0, vz), + self.zvgridpath)) + self.axes[axisname].hidden = not self.px0show and not self.py1show elif axisname == "z4": - self.axes["z4"].setpositioner(positioner.flexlineaxispos_pt(lambda vz: self.vpos_pt(1, 1, vz), - lambda vz: self.vtickdirection(1, 1, vz, 0, 0, vz), - self.zvgridpath)) + self.axes[axisname].setpositioner(positioner.flexlineaxispos_pt(lambda vz: self.vpos_pt(1, 1, vz), + lambda vz: self.vtickdirection(1, 1, vz, 0, 0, vz), + self.zvgridpath)) + self.axes[axisname].hidden = not self.px1show and not self.py1show else: raise NotImplementedError("4 axis per dimension supported only") @@ -880,7 +1087,10 @@ self.dolayout() self.dobackground() for axis in self.axes.values(): - self.insert(axis.canvas) + if axis.hidden: + self.layer("hiddenaxes").insert(axis.canvas) + else: + self.layer("axes").insert(axis.canvas) def dokey(self): if self.did(self.dokey): diff -Nru pyx-0.11.1/pyx/graph/style.py pyx-0.12.1/pyx/graph/style.py --- pyx-0.11.1/pyx/graph/style.py 2011-05-18 09:09:34.000000000 +0000 +++ pyx-0.12.1/pyx/graph/style.py 2012-10-26 09:00:00.000000000 +0000 @@ -1,9 +1,9 @@ # -*- encoding: utf-8 -*- # # -# Copyright (C) 2002-2004 Jörg Lehmann +# Copyright (C) 2002-2012 Jörg Lehmann # Copyright (C) 2003-2004 Michael Schindler -# Copyright (C) 2002-2011 André Wobst +# Copyright (C) 2002-2012 André Wobst # # This file is part of PyX (http://pyx.sourceforge.net/). # @@ -22,9 +22,12 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA -import math, warnings -from pyx import attr, deco, style, color, unit, canvas, path, mesh +import math, warnings, cStringIO +from pyx import attr, deco, bitmap, style, color, unit, canvas, path, mesh, pycompat, trafo from pyx import text as textmodule +from graph import registerdefaultprovider, graphx +import data as datamodule +import axis builtinrange = range @@ -38,7 +41,7 @@ all methods of this base class (e.g. this class is not an abstract class in any respect). - A style should never store private data by istance variables + A style should never store private data by instance variables (i.e. accessing self), but it should use the sharedata and privatedata instances instead. A style instance can be used multiple times with different sharedata and privatedata instances at the very same time. @@ -49,25 +52,29 @@ modified: - providesdata is a list of variable names a style offers via the sharedata instance. This list is used to determine whether - all needs of subsequent styles are fullfilled. Otherwise + all needs of subsequent styles are fulfilled. Otherwise getdefaultprovider should return a proper style to be used. - - needsdata is a list of variable names the style needs to access in the + - needsdata is a list of variable names the style needs to access in the sharedata instance. """ providesdata = [] # by default, we provide nothing needsdata = [] # and do not depend on anything - def columnnames(self, privatedata, sharedata, graph, columnnames): + def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames): """Set column information This method is used setup the column name information to be accessible to the style later on. The style should analyse the list of column names. The method should return a list of - column names which the style will make use of.""" + column names which the style will make use of. If a style + uses some column data to feed into an axis with a different + name, it should add an entry into the dataaxisnames dictionary + with key begin the column name and the value being the axis + name.""" return [] - def adjustaxis(self, privatedata, sharedata, graph, columnname, data): + def adjustaxis(self, privatedata, sharedata, graph, plotitem, columnname, data): """Adjust axis range This method is called in order to adjust the axis range to @@ -105,59 +112,108 @@ def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt): """Draw graph key""" + pass + + +class _marker: pass +class _autokeygraph: pass -# The following two methods are used to register and get a default provider -# for keys. A key is a variable name in sharedata. A provider is a style -# which creates variables in sharedata. - -_defaultprovider = {} - -def registerdefaultprovider(style, keys): - """sets a style as a default creator for sharedata variables 'keys'""" - for key in keys: - assert key in style.providesdata, "key not provided by style" - # we might allow for overwriting the defaults, i.e. the following is not checked: - # assert key in _defaultprovider.keys(), "default provider already registered for key" - _defaultprovider[key] = style - -def getdefaultprovider(key): - """returns a style, which acts as a default creator for the - sharedata variable 'key'""" - return _defaultprovider[key] +class _keygraphstyle(_style): + + autographkey = _autokeygraph + + def __init__(self, colorname="color", gradient=color.gradient.Grey, coloraxis=None, keygraph=_autokeygraph): + self.colorname = colorname + self.gradient = gradient + self.coloraxis = coloraxis + self.keygraph = keygraph + + def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames): + return [self.colorname] + + def adjustaxis(self, privatedata, sharedata, graph, plotitem, columnname, data): + if columnname == self.colorname: + if self.keygraph is None: + # we always need a keygraph, but we might not show it + if self.coloraxis is None: + coloraxis = axis.lin() + else: + coloraxis = self.coloraxis + privatedata.keygraph = graphx(length=10, direction="vertical", x=coloraxis) + elif self.keygraph is _autokeygraph: + if self.coloraxis is None: + coloraxis = axis.lin(title=plotitem.title) + plotitem.title = None # Huui!? + else: + coloraxis = self.coloraxis + privatedata.keygraph = graphx(x=coloraxis, **graph.autokeygraphattrs()) + else: + privatedata.keygraph = self.keygraph + # TODO: we shouldn't have multiple plotitems + privatedata.keygraph.plot(datamodule.values(x=data), [gradient(gradient=self.gradient)]) + + def color(self, privatedata, c): + vc = privatedata.keygraph.axes["x"].convert(c) + if vc < 0: + warnings.warn("gradiend color range is exceeded (lower bound)") + vc = 0 + if vc > 1: + warnings.warn("gradiend color range is exceeded (upper bound)") + vc = 1 + return self.gradient.getcolor(vc) + + def donedrawpoints(self, privatedata, sharedata, graph): + if self.keygraph is _autokeygraph: + graph.layer("key").insert(privatedata.keygraph, [graph.autokeygraphtrafo(privatedata.keygraph)]) class pos(_style): providesdata = ["vpos", "vposmissing", "vposavailable", "vposvalid", "poscolumnnames"] - def __init__(self, epsilon=1e-10): + def __init__(self, usenames={}, epsilon=1e-10): + self.usenames = usenames self.epsilon = epsilon - def columnnames(self, privatedata, sharedata, graph, columnnames): - sharedata.poscolumnnames = [] - sharedata.vposmissing = [] + def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames): + privatedata.poscolumnnames = [] + privatedata.vposmissing = [] + privatedata.axisnames = {} for count, axisnames in enumerate(graph.axesnames): for axisname in axisnames: + try: + usename = self.usenames[axisname] + except KeyError: + usename = axisname for columnname in columnnames: - if axisname == columnname: - sharedata.poscolumnnames.append(columnname) - if len(sharedata.poscolumnnames) > count+1: + if usename == columnname: + privatedata.poscolumnnames.append(columnname) + privatedata.axisnames[columnname] = axisname + if len(privatedata.poscolumnnames) > count+1: raise ValueError("multiple axes per graph dimension") - elif len(sharedata.poscolumnnames) < count+1: - sharedata.vposmissing.append(count) - sharedata.poscolumnnames.append(None) - return [columnname for columnname in sharedata.poscolumnnames if columnname is not None] - - def adjustaxis(self, privatedata, sharedata, graph, columnname, data): - if columnname in sharedata.poscolumnnames: - graph.axes[columnname].adjustaxis(data) + elif len(privatedata.poscolumnnames) < count+1: + privatedata.vposmissing.append(count) + privatedata.poscolumnnames.append(None) + # Make poscolumnnames and vposmissing available to the outside, + # but keep a private reference. A copy is not needed, because + # the data is not altered in place, but might be exchanged my a + # later, different pos style in the styles list (due to different + # usenames). + sharedata.poscolumnnames = privatedata.poscolumnnames + sharedata.vposmissing = privatedata.vposmissing + dataaxisnames.update(privatedata.axisnames) + return [columnname for columnname in privatedata.poscolumnnames if columnname is not None] + + def adjustaxis(self, privatedata, sharedata, graph, plotitem, columnname, data): + if columnname in privatedata.axisnames: + graph.axes[privatedata.axisnames[columnname]].adjustaxis(data) def initdrawpoints(self, privatedata, sharedata, graph): sharedata.vpos = [None]*(len(graph.axesnames)) - privatedata.pointpostmplist = [[columnname, index, graph.axes[columnname]] # temporarily used by drawpoint only - for index, columnname in enumerate([columnname for columnname in sharedata.poscolumnnames if columnname is not None])] - for missing in sharedata.vposmissing: + privatedata.pointpostmplist = [[columnname, index, graph.axes[privatedata.axisnames[columnname]]] # temporarily used by drawpoint only + for index, columnname in enumerate([columnname for columnname in privatedata.poscolumnnames if columnname is not None])] + for missing in privatedata.vposmissing: for pointpostmp in privatedata.pointpostmplist: if pointpostmp[1] >= missing: pointpostmp[1] += 1 @@ -204,7 +260,7 @@ else: return self._numberofbits(mask >> 1) - def columnnames(self, privatedata, sharedata, graph, columnnames): + def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames): usecolumns = [] privatedata.rangeposcolumns = [] sharedata.vrangemissing = [] @@ -236,6 +292,7 @@ addusecolumns = 0 if addusecolumns: usecolumns.append(columnname) + dataaxisnames[columnname] = axisname if mask & (self.mask_min | self.mask_max | self.mask_dmin | self.mask_dmax | self.mask_d): if (self._numberofbits(mask & (self.mask_min | self.mask_dmin | self.mask_d)) > 1 or self._numberofbits(mask & (self.mask_max | self.mask_dmax | self.mask_d)) > 1): @@ -260,11 +317,12 @@ sharedata.vrangemaxmissing.append(count) return usecolumns - def adjustaxis(self, privatedata, sharedata, graph, columnname, data): - if columnname in [c + "min" for a, c, m in privatedata.rangeposcolumns if m & self.mask_min]: - graph.axes[columnname[:-3]].adjustaxis(data) - if columnname in [c + "max" for a, c, m in privatedata.rangeposcolumns if m & self.mask_max]: - graph.axes[columnname[:-3]].adjustaxis(data) + def adjustaxis(self, privatedata, sharedata, graph, plotitem, columnname, data): + for axisname, usename, mask in privatedata.rangeposcolumns: + if columnname == usename + "min" and mask & self.mask_min: + graph.axes[axisname].adjustaxis(data) + if columnname == usename + "max" and mask & self.mask_max: + graph.axes[axisname].adjustaxis(data) # delta handling: fill rangeposdeltacolumns for axisname, usename, mask in privatedata.rangeposcolumns: @@ -387,7 +445,7 @@ needsdata = ["vposmissing"] - def columnnames(self, privatedata, sharedata, graph, columnnames): + def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames): if len(sharedata.vposmissing): raise ValueError("incomplete position information") return [] @@ -451,7 +509,7 @@ privatedata.symbol(privatedata.symbolcanvas, x_pt, y_pt, privatedata.size_pt, privatedata.symbolattrs) def donedrawpoints(self, privatedata, sharedata, graph): - graph.insert(privatedata.symbolcanvas) + graph.layer("data").insert(privatedata.symbolcanvas) def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt): if privatedata.symbolattrs is not None: @@ -633,7 +691,7 @@ def donedrawpoints(self, privatedata, sharedata, graph): path = self.donepointstopath(privatedata) if privatedata.lineattrs is not None and len(path): - graph.stroke(path, privatedata.lineattrs) + graph.layer("data").stroke(path, privatedata.lineattrs) def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt): if privatedata.lineattrs is not None: @@ -660,7 +718,7 @@ else: privatedata.lineattrs = None - def adjustaxis(self, privatedata, sharedata, graph, columnname, data): + def adjustaxis(self, privatedata, sharedata, graph, plotitem, columnname, data): if self.fromvalue is not None: try: i = sharedata.poscolumnnames.index(columnname) @@ -681,8 +739,8 @@ if privatedata.vfromvalue > 1: privatedata.vfromvalue = 1 if self.frompathattrs is not None and privatedata.insertfrompath: - graph.stroke(graph.axes[valueaxisname].vgridpath(privatedata.vfromvalue), - self.defaultfrompathattrs + self.frompathattrs) + graph.layer("data").stroke(graph.axes[valueaxisname].vgridpath(privatedata.vfromvalue), + self.defaultfrompathattrs + self.frompathattrs) else: privatedata.vfromvalue = 0 @@ -693,7 +751,7 @@ privatedata.impulsescanvas.stroke(graph.vgeodesic(*(vpos + sharedata.vpos)), privatedata.lineattrs) def donedrawpoints(self, privatedata, sharedata, graph): - graph.insert(privatedata.impulsescanvas) + graph.layer("data").insert(privatedata.impulsescanvas) def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt): if privatedata.lineattrs is not None: @@ -713,7 +771,7 @@ self.errorbarattrs = errorbarattrs self.epsilon = epsilon - def columnnames(self, privatedata, sharedata, graph, columnnames): + def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames): for i in sharedata.vposmissing: if i in sharedata.vrangeminmissing and i in sharedata.vrangemaxmissing: raise ValueError("position and range for a graph dimension missing") @@ -774,7 +832,7 @@ def donedrawpoints(self, privatedata, sharedata, graph): if privatedata.errorbarattrs is not None: - graph.insert(privatedata.errorbarcanvas) + graph.layer("data").insert(privatedata.errorbarcanvas) class text(_styleneedingpointpos): @@ -795,7 +853,7 @@ self.textdy = textdy self.textattrs = textattrs - def columnnames(self, privatedata, sharedata, graph, columnnames): + def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames): if self.textname not in columnnames: raise ValueError("column '%s' missing" % self.textname) names = [self.textname] @@ -807,7 +865,7 @@ if self.dyname not in columnnames: raise ValueError("column '%s' missing" % self.dyname) names.append(self.dyname) - return names + _styleneedingpointpos.columnnames(self, privatedata, sharedata, graph, columnnames) + return names + _styleneedingpointpos.columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames) def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal): if self.textattrs is not None: @@ -841,7 +899,7 @@ dy_pt = privatedata.textdy_pt else: dy_pt = float(point[self.dyname]) * privatedata.dyunit_pt - graph.text_pt(x_pt + dx_pt, y_pt + dy_pt, text, privatedata.textattrs) + graph.layer("data").text_pt(x_pt + dx_pt, y_pt + dy_pt, text, privatedata.textattrs) def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt): raise RuntimeError("Style currently doesn't provide a graph key") @@ -863,14 +921,14 @@ self.epsilon = epsilon self.decorator = decorator - def columnnames(self, privatedata, sharedata, graph, columnnames): + def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames): if len(graph.axesnames) != 2: raise ValueError("arrow style restricted on two-dimensional graphs") if "size" not in columnnames: raise ValueError("size missing") if "angle" not in columnnames: raise ValueError("angle missing") - return ["size", "angle"] + _styleneedingpointpos.columnnames(self, privatedata, sharedata, graph, columnnames) + return ["size", "angle"] + _styleneedingpointpos.columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames) def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal): if self.lineattrs is not None: @@ -909,30 +967,25 @@ privatedata.arrowcanvas.stroke(path.line_pt(x1, y1, x2, y2), privatedata.lineattrs) def donedrawpoints(self, privatedata, sharedata, graph): - graph.insert(privatedata.arrowcanvas) + graph.layer("data").insert(privatedata.arrowcanvas) def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt): raise RuntimeError("Style currently doesn't provide a graph key") -class rect(_style): +class rect(_keygraphstyle): needsdata = ["vrange", "vrangeminmissing", "vrangemaxmissing"] - def __init__(self, gradient=color.gradient.Grey): - self.gradient = gradient - - def columnnames(self, privatedata, sharedata, graph, columnnames): + def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames): if len(graph.axesnames) != 2: - raise TypeError("arrow style restricted on two-dimensional graphs") - if "color" not in columnnames: - raise ValueError("color missing") + raise TypeError("rect style restricted on two-dimensional graphs") if len(sharedata.vrangeminmissing) + len(sharedata.vrangemaxmissing): raise ValueError("incomplete range") - return ["color"] + return _keygraphstyle.columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames) def initdrawpoints(self, privatedata, sharedata, graph): - privatedata.rectcanvas = graph.insert(canvas.canvas()) + privatedata.rectcanvas = graph.layer("filldata").insert(canvas.canvas()) def drawpoint(self, privatedata, sharedata, graph, point): xvmin = sharedata.vrange[0][0] @@ -956,10 +1009,7 @@ p.append(graph.vgeodesic_el(xvmax, yvmax, xvmin, yvmax)) p.append(graph.vgeodesic_el(xvmin, yvmax, xvmin, yvmin)) p.append(path.closepath()) - privatedata.rectcanvas.fill(p, [self.gradient.getcolor(point["color"])]) - - def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt): - raise RuntimeError("Style currently doesn't provide a graph key") + privatedata.rectcanvas.fill(p, [self.color(privatedata, point["color"])]) class histogram(_style): @@ -981,7 +1031,7 @@ self.autohistogrampointpos = autohistogrampointpos self.epsilon = epsilon - def columnnames(self, privatedata, sharedata, graph, columnnames): + def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames): if len(graph.axesnames) != 2: raise TypeError("histogram style restricted on two-dimensional graphs") privatedata.rangeaxisindex = None @@ -1000,7 +1050,7 @@ privatedata.autohistogram = 0 return [] - def adjustaxis(self, privatedata, sharedata, graph, columnname, data): + def adjustaxis(self, privatedata, sharedata, graph, plotitem, columnname, data): if privatedata.autohistogram and columnname == sharedata.poscolumnnames[privatedata.rangeaxisindex]: if len(data) == 1: raise ValueError("several data points needed for automatic histogram width calculation") @@ -1243,8 +1293,8 @@ privatedata.vfromvalue = 1 privatedata.vfromvaluecut = 1 if self.frompathattrs is not None and privatedata.insertfrompath: - graph.stroke(graph.axes[valueaxisname].vgridpath(privatedata.vfromvalue), - self.defaultfrompathattrs + self.frompathattrs) + graph.layer("data").stroke(graph.axes[valueaxisname].vgridpath(privatedata.vfromvalue), + self.defaultfrompathattrs + self.frompathattrs) else: privatedata.vfromvalue = 0 @@ -1295,7 +1345,7 @@ def donedrawpoints(self, privatedata, sharedata, graph): self.drawvalue(privatedata, sharedata, graph, None, None, None) if privatedata.lineattrs is not None and len(privatedata.path): - graph.draw(privatedata.path, privatedata.lineattrs) + graph.layer("data").draw(privatedata.path, privatedata.lineattrs) def key_pt(self, privatedata, sharedata, graph, x_pt, y_pt, width_pt, height_pt): if privatedata.lineattrs is not None: @@ -1317,7 +1367,7 @@ self.frompathattrs = frompathattrs self.epsilon = epsilon - def columnnames(self, privatedata, sharedata, graph, columnnames): + def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames): sharedata.barposcolumnnames = [] sharedata.barvalueindex = None for dimension, axisnames in enumerate(graph.axesnames): @@ -1352,7 +1402,7 @@ else: return value, subvalue - def adjustaxis(self, privatedata, sharedata, graph, columnname, data): + def adjustaxis(self, privatedata, sharedata, graph, plotitem, columnname, data): try: i = sharedata.barposcolumnnames.index(columnname) except ValueError: @@ -1381,8 +1431,8 @@ if privatedata.vfromvalue > 1: privatedata.vfromvalue = 1 if self.frompathattrs is not None and privatedata.insertfrompath: - graph.stroke(graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].vgridpath(privatedata.vfromvalue), - self.defaultfrompathattrs + self.frompathattrs) + graph.layer("data").stroke(graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].vgridpath(privatedata.vfromvalue), + self.defaultfrompathattrs + self.frompathattrs) else: privatedata.vfromvalue = 0 @@ -1424,12 +1474,12 @@ self.epsilon = epsilon self.addontop = addontop - def columnnames(self, privatedata, sharedata, graph, columnnames): + def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames): if self.stackname not in columnnames: raise ValueError("column '%s' missing" % self.stackname) return [self.stackname] - def adjustaxis(self, privatedata, sharedata, graph, columnname, data): + def adjustaxis(self, privatedata, sharedata, graph, plotitem, columnname, data): if columnname == self.stackname: graph.axes[sharedata.barposcolumnnames[sharedata.barvalueindex]].adjustaxis(data) @@ -1476,14 +1526,14 @@ def lighting(self, angle, zindex): return self.gradient.getcolor(0.7-0.4*abs(angle)+0.1*zindex) - def columnnames(self, privatedata, sharedata, graph, columnnames): + def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames): return [] def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal): privatedata.barattrs = attr.selectattrs(self.defaultbarattrs + self.barattrs, selectindex, selecttotal) def initdrawpoints(self, privatedata, sharedata, graph): - privatedata.barcanvas = graph.insert(canvas.canvas()) + privatedata.barcanvas = graph.layer("filldata").insert(canvas.canvas()) sharedata.stackedbardraw = 1 privatedata.stackedbar = sharedata.stackedbar privatedata.todraw = [] @@ -1679,7 +1729,7 @@ self.addpoint(privatedata, graph.vpos_pt, *data) p = self.donepointstopath(privatedata) if len(p): - graph.stroke(p, privatedata.gridattrs) + graph.layer("data").stroke(p, privatedata.gridattrs) if self.gridlines2: for value1 in values1: data2 = sharedata.data12[value1] @@ -1693,26 +1743,22 @@ self.addpoint(privatedata, graph.vpos_pt, *data) p = self.donepointstopath(privatedata) if len(p): - graph.stroke(p, privatedata.gridattrs) + graph.layer("data").stroke(p, privatedata.gridattrs) -class surface(_style): +class surface(_keygraphstyle): needsdata = ["values1", "values2", "data12", "data21"] - def __init__(self, colorname="color", gradient=color.gradient.Grey, mincolor=None, maxcolor=None, - gridlines1=0.05, gridlines2=0.05, gridcolor=None, - backcolor=color.gray.black): - self.colorname = colorname - self.gradient = gradient - self.mincolor = mincolor - self.maxcolor = maxcolor + def __init__(self, gridlines1=0.05, gridlines2=0.05, gridcolor=None, + backcolor=color.gray.black, **kwargs): + _keygraphstyle.__init__(self, **kwargs) self.gridlines1 = gridlines1 self.gridlines2 = gridlines2 self.gridcolor = gridcolor self.backcolor = backcolor - colorspacestring = gradient.getcolor(0).colorspacestring() + colorspacestring = self.gradient.getcolor(0).colorspacestring() if self.gridcolor is not None and self.gridcolor.colorspacestring() != colorspacestring: raise RuntimeError("colorspace mismatch (gradient/grid)") if self.backcolor is not None and self.backcolor.colorspacestring() != colorspacestring: @@ -1729,10 +1775,10 @@ return self.backcolor return self.gradient.getcolor(0.7-0.4*abs(angle)+0.1*zindex) - def columnnames(self, privatedata, sharedata, graph, columnnames): + def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames): privatedata.colorize = self.colorname in columnnames if privatedata.colorize: - return [self.colorname] + return _keygraphstyle.columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames) return [] def initdrawpoints(self, privatedata, sharedata, graph): @@ -1794,11 +1840,6 @@ sortElements = [(zindex, i) for i, zindex in enumerate(sortElements)] sortElements.sort() - mincolor, maxcolor = privatedata.mincolor, privatedata.maxcolor - if self.mincolor is not None: - mincolor = self.mincolor - if self.maxcolor is not None: - maxcolor = self.maxcolor nodes = [] elements = [] for value1a, value1b in zip(values1[:-1], values1[1:]): @@ -1836,25 +1877,16 @@ x4_pt, y4_pt = graph.vpos_pt(*v4) x5_pt, y5_pt = graph.vpos_pt(*v5) if privatedata.colorize: - def colorfromgradient(c): - vc = (c - mincolor) / float(maxcolor - mincolor) - if vc < 0: - warnings.warn("gradiend color range is exceeded due to mincolor setting") - vc = 0 - if vc > 1: - warnings.warn("gradiend color range is exceeded due to maxcolor setting") - vc = 1 - return self.gradient.getcolor(vc) c1 = privatedata.colors[value1a][value2a] c2 = privatedata.colors[value1a][value2b] c3 = privatedata.colors[value1b][value2a] c4 = privatedata.colors[value1b][value2b] c5 = self.midcolor(c1, c2, c3, c4) - c1a = c1b = colorfromgradient(c1) - c2a = c2c = colorfromgradient(c2) - c3b = c3d = colorfromgradient(c3) - c4c = c4d = colorfromgradient(c4) - c5a = c5b = c5c = c5d = colorfromgradient(c5) + c1a = c1b = self.color(privatedata, c1) + c2a = c2c = self.color(privatedata, c2) + c3b = c3d = self.color(privatedata, c3) + c4c = c4d = self.color(privatedata, c4) + c5a = c5b = c5c = c5d = self.color(privatedata, c5) if self.backcolor is not None and sign*graph.vangle(*(v1+v2+v5)) < 0: c1a = c2a = c5a = self.backcolor if self.backcolor is not None and sign*graph.vangle(*(v3+v1+v5)) < 0: @@ -1915,4 +1947,208 @@ mesh.node_pt(graph.vpos_pt(*v4), self.gridcolor), mesh.node_pt(graph.vpos_pt(*v4f), self.gridcolor)))) m = mesh.mesh(elements, check=0) - graph.insert(m) + graph.layer("filldata").insert(m) + + if privatedata.colorize: + _keygraphstyle.donedrawpoints(self, privatedata, sharedata, graph) + + +class density(_keygraphstyle): + + needsdata = ["values1", "values2", "data12", "data21"] + + def __init__(self, epsilon=1e-10, **kwargs): + _keygraphstyle.__init__(self, **kwargs) + self.epsilon = epsilon + + def initdrawpoints(self, privatedata, sharedata, graph): + privatedata.colors = {} + privatedata.vfixed = [None]*len(graph.axesnames) + + def drawpoint(self, privatedata, sharedata, graph, point): + try: + color = point[self.colorname] + 0 + except: + pass + else: + privatedata.colors.setdefault(sharedata.value1, {})[sharedata.value2] = color + if len(privatedata.vfixed) > 2 and sharedata.vposavailable: + for i, (v1, v2) in enumerate(zip(privatedata.vfixed, sharedata.vpos)): + if i != sharedata.index1 and i != sharedata.index2: + if v1 is None: + privatedata.vfixed[i] = v2 + elif abs(v1-v2) > self.epsilon: + raise ValueError("data must be in a plane for the bitmap style") + + def donedrawpoints(self, privatedata, sharedata, graph): + privatedata.keygraph.doaxes() + + values1 = pycompat.sorted(sharedata.values1.keys()) + values2 = pycompat.sorted(sharedata.values2.keys()) + def equidistant(values): + l = len(values) - 1 + if l < 1: + raise ValueError("several data points required by the bitmap style in each dimension") + range = values[-1] - values[0] + for i, value in enumerate(values): + if abs(value - values[0] - i * range / l) > self.epsilon: + raise ValueError("data must be equidistant for the bitmap style") + equidistant(values1) + equidistant(values2) + needalpha = False + for value2 in values2: + for value1 in values1: + try: + available, valid, v = sharedata.data12[value1][value2] + except KeyError: + needalpha = True + break + if not available: + needalpha = True + continue + else: + continue + break + mode = {"/DeviceGray": "L", + "/DeviceRGB": "RGB", + "/DeviceCMYK": "CMYK"}[self.gradient.getcolor(0).colorspacestring()] + if needalpha: + mode = "A" + mode + empty = "\0"*len(mode) + data = cStringIO.StringIO() + for value2 in values2: + for value1 in values1: + try: + available, valid, v = sharedata.data12[value1][value2] + except KeyError: + data.write(empty) + continue + if not available: + data.write(empty) + continue + c = privatedata.colors[value1][value2] + c = self.color(privatedata, c) + if needalpha: + data.write(chr(255)) + data.write(c.to8bitstring()) + i = bitmap.image(len(values1), len(values2), mode, data.getvalue()) + + v1enlargement = (values1[-1]-values1[0])*0.5/len(values1) + v2enlargement = (values2[-1]-values2[0])*0.5/len(values2) + + privatedata.vfixed[sharedata.index1] = values1[0]-v1enlargement + privatedata.vfixed[sharedata.index2] = values2[-1]+v2enlargement + x1_pt, y1_pt = graph.vpos_pt(*privatedata.vfixed) + privatedata.vfixed[sharedata.index1] = values1[-1]+v1enlargement + privatedata.vfixed[sharedata.index2] = values2[-1]+v2enlargement + x2_pt, y2_pt = graph.vpos_pt(*privatedata.vfixed) + privatedata.vfixed[sharedata.index1] = values1[0]-v1enlargement + privatedata.vfixed[sharedata.index2] = values2[0]-v2enlargement + x3_pt, y3_pt = graph.vpos_pt(*privatedata.vfixed) + t = trafo.trafo_pt(((x2_pt-x1_pt, x3_pt-x1_pt), (y2_pt-y1_pt, y3_pt-y1_pt)), (x1_pt, y1_pt)) + + privatedata.vfixed[sharedata.index1] = values1[-1]+v1enlargement + privatedata.vfixed[sharedata.index2] = values2[0]-v2enlargement + vx4, vy4 = t.inverse().apply_pt(*graph.vpos_pt(*privatedata.vfixed)) + if abs(vx4 - 1) > self.epsilon or abs(vy4 - 1) > self.epsilon: + raise ValueError("invalid graph layout for bitmap style (bitmap positioning by affine transformation failed)") + + p = path.path() + privatedata.vfixed[sharedata.index1] = 0 + privatedata.vfixed[sharedata.index2] = 0 + p.append(path.moveto_pt(*graph.vpos_pt(*privatedata.vfixed))) + vfixed2 = privatedata.vfixed + privatedata.vfixed + vfixed2[sharedata.index1] = 0 + vfixed2[sharedata.index2] = 0 + vfixed2[sharedata.index1 + len(graph.axesnames)] = 1 + vfixed2[sharedata.index2 + len(graph.axesnames)] = 0 + p.append(graph.vgeodesic_el(*vfixed2)) + vfixed2[sharedata.index1] = 1 + vfixed2[sharedata.index2] = 0 + vfixed2[sharedata.index1 + len(graph.axesnames)] = 1 + vfixed2[sharedata.index2 + len(graph.axesnames)] = 1 + p.append(graph.vgeodesic_el(*vfixed2)) + vfixed2[sharedata.index1] = 1 + vfixed2[sharedata.index2] = 1 + vfixed2[sharedata.index1 + len(graph.axesnames)] = 0 + vfixed2[sharedata.index2 + len(graph.axesnames)] = 1 + p.append(graph.vgeodesic_el(*vfixed2)) + vfixed2[sharedata.index1] = 0 + vfixed2[sharedata.index2] = 1 + vfixed2[sharedata.index1 + len(graph.axesnames)] = 0 + vfixed2[sharedata.index2 + len(graph.axesnames)] = 0 + p.append(graph.vgeodesic_el(*vfixed2)) + p.append(path.closepath()) + + c = canvas.canvas([canvas.clip(p)]) + b = bitmap.bitmap_trafo(t, i) + c.insert(b) + graph.layer("filldata").insert(c) + + _keygraphstyle.donedrawpoints(self, privatedata, sharedata, graph) + + + +class gradient(_style): + + defaultbarattrs = [color.gradient.Rainbow, deco.stroked([color.grey.black])] + + def __init__(self, gradient=color.gradient.Gray, resolution=100, columnname="x"): + self.gradient = gradient + self.resolution = resolution + self.columnname = columnname + + def columnnames(self, privatedata, sharedata, graph, columnnames, dataaxisnames): + return [self.columnname] + + def adjustaxis(self, privatedata, sharedata, graph, plotitem, columnname, data): + graph.axes[self.columnname].adjustaxis(data) + + def selectstyle(self, privatedata, sharedata, graph, selectindex, selecttotal): + pass + + def initdrawpoints(self, privatedata, sharedata, graph): + pass + + def drawpoint(self, privatedata, sharedata, graph, point): + pass + + def donedrawpoints(self, privatedata, sharedata, graph): + mode = {"/DeviceGray": "L", + "/DeviceRGB": "RGB", + "/DeviceCMYK": "CMYK"}[self.gradient.getcolor(0).colorspacestring()] + data = cStringIO.StringIO() + for i in builtinrange(self.resolution): + c = self.gradient.getcolor(i*1.0/self.resolution) + data.write(c.to8bitstring()) + i = bitmap.image(self.resolution, 1, mode, data.getvalue()) + + llx_pt, lly_pt = graph.vpos_pt(0) + urx_pt, ury_pt = graph.vpos_pt(1) + + if graph.direction == "horizontal": + lly_pt -= 0.5*graph.height_pt + ury_pt += 0.5*graph.height_pt + else: + llx_pt -= 0.5*graph.width_pt + urx_pt += 0.5*graph.width_pt + + c = canvas.canvas([canvas.clip(path.rect_pt(llx_pt, lly_pt, urx_pt-llx_pt, ury_pt-lly_pt))]) + + if graph.direction == "horizontal": + add_pt = (urx_pt-llx_pt)*0.5/(self.resolution-1) + llx_pt -= add_pt + urx_pt += add_pt + else: + add_pt = (ury_pt-lly_pt)*0.5/(self.resolution-1) + lly_pt -= add_pt + ury_pt += add_pt + + if graph.direction == "horizontal": + t = trafo.trafo_pt(((urx_pt-llx_pt,0 ), (0, ury_pt-lly_pt)), (llx_pt, lly_pt)) + else: + t = trafo.trafo_pt(((0, urx_pt-llx_pt), (ury_pt-lly_pt, 0)), (llx_pt, lly_pt)) + + b = bitmap.bitmap_trafo(t, i) + c.insert(b) + graph.layer("filldata").insert(c) diff -Nru pyx-0.11.1/pyx/mesh.py pyx-0.12.1/pyx/mesh.py --- pyx-0.11.1/pyx/mesh.py 2011-05-18 09:09:35.000000000 +0000 +++ pyx-0.12.1/pyx/mesh.py 2012-10-17 15:31:57.000000000 +0000 @@ -113,12 +113,8 @@ import Image c = canvas.canvas() c.insert(self) - fd, fname = tempfile.mkstemp() - f = os.fdopen(fd, "wb") - f.close() - c.pipeGS(fname, device="pngalpha", resolution=writer.mesh_as_bitmap_resolution) - i = Image.open(fname) - os.unlink(fname) + i = Image.open(c.pipeGS("pngalpha", resolution=writer.mesh_as_bitmap_resolution, seekable=True)) + i.load() b = bitmap.bitmap_pt(self.bbox().llx_pt, self.bbox().lly_pt, i) # we slightly shift the bitmap to re-center it, as the bitmap might contain some additional border # unfortunately we need to construct another bitmap instance for that ... @@ -139,7 +135,7 @@ thisbbox.llx_pt, thisbbox.urx_pt, thisbbox.lly_pt, thisbbox.ury_pt, " ".join(["0 1" for value in self.elements[0].nodes[0].value.to8bitstring()]))) file.write(binascii.b2a_hex(zlib.compress(self.data(thisbbox)))) - file.write("\n") + file.write(">\n") def processPDF(self, file, writer, context, registry, bbox): if writer.mesh_as_bitmap: @@ -147,12 +143,8 @@ import Image c = canvas.canvas() c.insert(self) - fd, fname = tempfile.mkstemp() - f = os.fdopen(fd, "wb") - f.close() - c.pipeGS(fname, device="pngalpha", resolution=writer.mesh_as_bitmap_resolution) - i = Image.open(fname) - os.unlink(fname) + i = Image.open(c.pipeGS("pngalpha", resolution=writer.mesh_as_bitmap_resolution, seekable=True)) + i.load() b = bitmap.bitmap_pt(self.bbox().llx_pt, self.bbox().lly_pt, i) # we slightly shift the bitmap to re-center it, as the bitmap might contain some additional border # unfortunately we need to construct another bitmap instance for that ... diff -Nru pyx-0.11.1/pyx/metapost/__init__.py pyx-0.12.1/pyx/metapost/__init__.py --- pyx-0.11.1/pyx/metapost/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ pyx-0.12.1/pyx/metapost/__init__.py 2012-10-17 15:31:57.000000000 +0000 @@ -0,0 +1,23 @@ +# -*- encoding: utf-8 -*- +# +# +# Copyright (C) 2011 Michael Schindler +# +# This file is part of PyX (http://pyx.sourceforge.net/). +# +# PyX is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# PyX is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with PyX; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +__all__ = ["path", "mp_path"] + diff -Nru pyx-0.11.1/pyx/metapost/mp_path.py pyx-0.12.1/pyx/metapost/mp_path.py --- pyx-0.11.1/pyx/metapost/mp_path.py 1970-01-01 00:00:00.000000000 +0000 +++ pyx-0.12.1/pyx/metapost/mp_path.py 2012-10-17 15:31:57.000000000 +0000 @@ -0,0 +1,449 @@ +# -*- coding: ISO-8859-1 -*- +# +# Copyright (C) 2011 Michael Schindler +# +# This file is part of PyX (http://pyx.sourceforge.net/). +# +# PyX is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# PyX is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with PyX; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +from math import atan2, sin, cos, sqrt, pi + +################################################################################ +# Internal functions of MetaPost +# +# This file re-implements some of the functionality of MetaPost +# (http://tug.org/metapost). MetaPost was developed by John D. Hobby and +# others. The code of Metapost is in the public domain, which we understand as +# an implicit permission to reuse the code here (see the comment at +# http://www.gnu.org/licenses/license-list.html) +# +# This file is based on the MetaPost version distributed by TeXLive: +# svn://tug.org/texlive/trunk/Build/source/texk/web2c/mplibdir revision 22737 # +# (2011-05-31) +################################################################################ + +# from mplib.h: +mp_endpoint = 0 +mp_explicit = 1 +mp_given = 2 +mp_curl = 3 +mp_open = 4 +mp_end_cycle = 5 + +# from mpmath.c: +unity = 1.0 +two = 2.0 +fraction_half = 0.5 +fraction_one = 1.0 +fraction_three = 3.0 +one_eighty_deg = pi +three_sixty_deg = 2*pi + +def mp_make_choices(knots, epsilon): # <<< + """Implements mp_make_choices from metapost (mp.c)""" + # 334: If consecutive knots are equal, join them explicitly + p = knots + while True: + q = p.next + if p.rtype > mp_explicit and (p.x_pt-q.x_pt)**2 + (p.y_pt-q.y_pt)**2 < epsilon**2: + p.rtype = mp_explicit + if p.ltype == mp_open: + p.ltype = mp_curl + p.set_left_curl(unity) + q.ltype = mp_explicit + if q.rtype == mp_open: + q.rtype = mp_curl + q.set_right_curl(unity) + p.rx_pt = p.x_pt + q.lx_pt = p.x_pt + p.ry_pt = p.y_pt + q.ly_pt = p.y_pt + p = q + if p is knots: + break + + # 335: + # If there are no breakpoints, it is necessary to compute the direction angles around an entire cycle. + # In this case the mp left type of the first node is temporarily changed to end cycle. + # Find the first breakpoint, h, on the path + # insert an artificial breakpoint if the path is an unbroken cycle + h = knots + while True: + if h.ltype != mp_open or h.rtype != mp_open: + break + h = h.next + if h is knots: + h.ltype = mp_end_cycle + break + + p = h + while True: + # 336: + # Fill in the control points between p and the next breakpoint, then advance p to that breakpoint + q = p.next + if p.rtype >= mp_given: + while q.ltype == mp_open and q.rtype == mp_open: + q = q.next + # the breakpoints are now p and q + + # 346: + # Calculate the turning angles psi_k and the distances d(k, k+1) + # set n to the length of the path + k = 0 + s = p + n = knots.linked_len() + delta_x, delta_y, delta, psi = [], [], [], [None] + while True: + t = s.next + assert len(delta_x) == k + delta_x.append(t.x_pt - s.x_pt) + delta_y.append(t.y_pt - s.y_pt) + delta.append(mp_pyth_add(delta_x[k], delta_y[k])) + if k > 0: + sine = mp_make_fraction(delta_y[k-1], delta[k-1]) + cosine = mp_make_fraction(delta_x[k-1], delta[k-1]) + psi.append(mp_n_arg( + mp_take_fraction(delta_x[k], cosine) + mp_take_fraction(delta_y[k], sine), + mp_take_fraction(delta_y[k], cosine) - mp_take_fraction(delta_x[k], sine))) + k += 1 + s = t + if s == q: + n = k + if k >= n and s.ltype != mp_end_cycle: + break + if k == n: + psi.append(0) + else: + # for closed paths: + psi.append(psi[1]) + + # 347: Remove open types at the breakpoints + if q.ltype == mp_open: + delx_pt = q.rx_pt - q.x_pt + dely_pt = q.ry_pt - q.y_pt + if delx_pt**2 + dely_pt**2 < epsilon**2: + # use curl if the controls are not usable for giving an angle + q.ltype = mp_curl + q.set_left_curl(unity) + else: + q.ltype = mp_given + q.set_left_given(mp_n_arg(delx_pt, dely_pt)) + + if p.rtype == mp_open and p.ltype == mp_explicit: + delx_pt = p.x_pt - p.lx_pt + dely_pt = p.y_pt - p.ly_pt + if delx_pt**2 + dely_pt**2 < epsilon**2: + p.rtype = mp_curl + p.set_right_curl(unity) + else: + p.rtype = mp_given + p.set_right_given(mp_n_arg(delx_pt, dely_pt)) + + # call the internal solving routine + mp_solve_choices(p, q, n, delta_x, delta_y, delta, psi) + + elif p.rtype == mp_endpoint: + # 337: Give reasonable values for the unused control points between p and q + p.rx_pt = p.x_pt + p.ry_pt = p.y_pt + q.lx_pt = q.x_pt + q.ly_pt = q.y_pt + + p = q + if p is h: + break +# >>> +def mp_solve_choices(p, q, n, delta_x, delta_y, delta, psi): # <<< + """Implements mp_solve_choices form metapost (mp.c)""" + uu = [None]*(len(delta)+1) # relations between adjacent angles ("matrix" entries) + ww = [None]*len(uu) # additional matrix entries for the cyclic case + vv = [None]*len(uu) # angles ("rhs" entries) + theta = [None]*len(uu) # solution of the linear system of equations + # 348: + # the "matrix" is in tridiagonal form, the solution is obtained by Gaussian elimination. + # uu and ww are of type "fraction", vv and theta are of type "angle" + # k is the current knot number + # r, s, t registers for list traversal + k = 0 + s = p + r = 0 + while True: + t = s.next + if k == 0: # <<< + # 354: + # Get the linear equations started + # or return with the control points in place, if linear equations needn't be solved + + if s.rtype == mp_given: # <<< + if t.ltype == mp_given: + # 372: Reduce to simple case of two givens and return + aa = mp_n_arg(delta_x[0], delta_y[0]) + ct, st = mp_n_sin_cos(p.right_given() - aa) + cf, sf = mp_n_sin_cos(q.left_given() - aa) + mp_set_controls(p, q, delta_x[0], delta_y[0], st, ct, -sf, cf) + return + else: + # 362: + vv[0] = s.right_given() - mp_n_arg(delta_x[0], delta_y[0]) + vv[0] = reduce_angle(vv[0]) + uu[0] = 0 + ww[0] = 0 + # >>> + elif s.rtype == mp_curl: # <<< + if t.ltype == mp_curl: + # 373: (mp.pdf) Reduce to simple case of straight line and return + p.rtype = mp_explicit + q.ltype = mp_explicit + lt = abs(q.left_tension()) + rt = abs(p.right_tension()) + + ff = mp_make_fraction(unity, 3.0*rt) + p.rx_pt = p.x_pt + mp_take_fraction(delta_x[0], ff) + p.ry_pt = p.y_pt + mp_take_fraction(delta_y[0], ff) + + ff = mp_make_fraction(unity, 3.0*lt) + q.lx_pt = q.x_pt - mp_take_fraction(delta_x[0], ff) + q.ly_pt = q.y_pt - mp_take_fraction(delta_y[0], ff) + return + + else: # t.ltype != mp_curl + # 363: + cc = s.right_curl() + lt = abs(t.left_tension()) + rt = abs(s.right_tension()) + uu[0] = mp_curl_ratio(cc, rt, lt) + vv[0] = -mp_take_fraction(psi[1], uu[0]) + ww[0] = 0 + # >>> + elif s.rtype == mp_open: # <<< + uu[0] = 0 + vv[0] = 0 + ww[0] = fraction_one + # >>> + # end of 354 >>> + else: # k > 0 <<< + + if s.ltype == mp_end_cycle or s.ltype == mp_open: # <<< + # 356: Set up equation to match mock curvatures at z_k; + # then finish loop with theta_n adjusted to equal theta_0, if a + # cycle has ended + + # 357: Calculate the values + # aa = Ak/Bk, bb = Dk/Ck, dd = (3-alpha_{k-1})d(k,k+1), + # ee = (3-beta_{k+1})d(k-1,k), cc=(Bk-uk-Ak)/Bk + aa = mp_make_fraction(unity, 3.0*abs(r.right_tension()) - unity) + dd = mp_take_fraction(delta[k], + fraction_three - mp_make_fraction(unity, abs(r.right_tension()))) + bb = mp_make_fraction(unity, 3*abs(t.left_tension()) - unity) + ee = mp_take_fraction(delta[k-1], + fraction_three - mp_make_fraction(unity, abs(t.left_tension()))) + cc = fraction_one - mp_take_fraction(uu[k-1], aa) + + # 358: Calculate the ratio ff = Ck/(Ck + Bk - uk-1Ak) + dd = mp_take_fraction(dd, cc) + lt = abs(s.left_tension()) + rt = abs(s.right_tension()) + if lt < rt: + dd *= (lt/rt)**2 + elif lt > rt: + ee *= (rt/lt)**2 + ff = mp_make_fraction(ee, ee + dd) + + uu[k] = mp_take_fraction(ff, bb) + + # 359: Calculate the values of vk and wk + acc = -mp_take_fraction(psi[k+1], uu[k]) + if r.rtype == mp_curl: + ww[k] = 0 + vv[k] = acc - mp_take_fraction(psi[1], fraction_one - ff) + else: + ff = mp_make_fraction(fraction_one - ff, cc) + acc = acc - mp_take_fraction(psi[k], ff) + ff = mp_take_fraction(ff, aa) + vv[k] = acc - mp_take_fraction(vv[k-1], ff) + ww[k] = -mp_take_fraction(ww[k-1], ff) + + if s.ltype == mp_end_cycle: + # 360: Adjust theta_n to equal theta_0 and finish loop + + aa = 0 + bb = fraction_one + while True: + k -= 1 + if k == 0: + k = n + aa = vv[k] - mp_take_fraction(aa, uu[k]) + bb = ww[k] - mp_take_fraction(bb, uu[k]) + if k == n: + break + aa = mp_make_fraction(aa, fraction_one - bb) + theta[n] = aa + vv[0] = aa + for k in range(1, n): + vv[k] = vv[k] + mp_take_fraction(aa, ww[k]) + break + # >>> + elif s.ltype == mp_curl: # <<< + # 364: + cc = s.left_curl() + lt = abs(s.left_tension()) + rt = abs(r.right_tension()) + ff = mp_curl_ratio(cc, lt, rt) + theta[n] = -mp_make_fraction(mp_take_fraction(vv[n-1], ff), + fraction_one - mp_take_fraction(ff, uu[n-1])) + break + # >>> + elif s.ltype == mp_given: # <<< + # 361: + theta[n] = s.left_given() - mp_n_arg(delta_x[n-1], delta_y[n-1]) + theta[n] = reduce_angle(theta[n]) + break + # >>> + + # end of k == 0, k != 0 >>> + + r = s + s = t + k += 1 + + # 367: + # Finish choosing angles and assigning control points + for k in range(n-1, -1, -1): + theta[k] = vv[k] - mp_take_fraction(theta[k+1], uu[k]) + s = p + k = 0 + while True: + t = s.next + ct, st = mp_n_sin_cos(theta[k]) + cf, sf = mp_n_sin_cos(-psi[k+1]-theta[k+1]) + mp_set_controls(s, t, delta_x[k], delta_y[k], st, ct, sf, cf) + k += 1 + s = t + if k == n: + break +# >>> +def mp_n_arg(x, y): # <<< + return atan2(y, x) +# >>> +def mp_n_sin_cos(z): # <<< + """Given an integer z that is 2**20 times an angle theta in degrees, the + purpose of n sin cos(z) is to set x = r cos theta and y = r sin theta + (approximately), for some rather large number r. The maximum of x and y + will be between 2**28 and 2**30, so that there will be hardly any loss of + accuracy. Then x and y are divided by r.""" + # 67: mpmath.pdf + return cos(z), sin(z) +# >>> +def mp_set_controls(p, q, delta_x, delta_y, st, ct, sf, cf): # <<< + """The set controls routine actually puts the control points into a pair of + consecutive nodes p and q. Global variables are used to record the values + of sin(theta), cos(theta), sin(phi), and cos(phi) needed in this + calculation. + + See mp.pdf, item 370""" + lt = abs(q.left_tension()) + rt = abs(p.right_tension()) + rr = mp_velocity(st, ct, sf, cf, rt) + ss = mp_velocity(sf, cf, st, ct, lt) + if p.right_tension() < 0 or q.left_tension() < 0: + # 371: Decrease the velocities, if necessary, to stay inside the bounding triangle + # this is the only place where the sign of the tension counts + if (st >= 0 and sf >= 0) or (st <= 0 and sf <= 0): + sine = mp_take_fraction(abs(st), cf) + mp_take_fraction(abs(sf), ct) # sin(theta+phi) + if sine > 0: + #sine = mp_take_fraction(sine, fraction_one + unity) # safety factor + sine *= 1.00024414062 # safety factor + if p.right_tension() < 0: + if mp_ab_vs_cd(abs(sf), fraction_one, rr, sine) < 0: + rr = mp_make_fraction(abs(sf), sine) + if q.left_tension() < 0: + if mp_ab_vs_cd(abs(st), fraction_one, ss, sine) < 0: + ss = mp_make_fraction(abs(st), sine) + + p.rx_pt = p.x_pt + mp_take_fraction(mp_take_fraction(delta_x, ct) - mp_take_fraction(delta_y, st), rr) + p.ry_pt = p.y_pt + mp_take_fraction(mp_take_fraction(delta_y, ct) + mp_take_fraction(delta_x, st), rr) + q.lx_pt = q.x_pt - mp_take_fraction(mp_take_fraction(delta_x, cf) + mp_take_fraction(delta_y, sf), ss) + q.ly_pt = q.y_pt - mp_take_fraction(mp_take_fraction(delta_y, cf) - mp_take_fraction(delta_x, sf), ss) + p.rtype = mp_explicit + q.ltype = mp_explicit +# >>> +def mp_make_fraction(p, q): # <<< + # 17: mpmath.pdf + """The make fraction routine produces the fraction equivalent of p/q, given + integers p and q; it computes the integer f = floor(2**28 p/q + 1/2), when + p and q are positive. + + In machine language this would simply be (2**28*p)div q""" + return p/q +# >>> +def mp_take_fraction(q, f): # <<< + # 20: mpmath.pdf + """The dual of make fraction is take fraction, which multiplies a given + integer q by a fraction f. When the operands are positive, it computes + p = floor(q*f/2**28 + 1/2), a symmetric function of q and f.""" + return q*f +# >>> +def mp_pyth_add(a, b): # <<< + # 44: mpmath.pdf + """Pythagorean addition sqrt(a**2 + b**2) is implemented by an elegant + iterative scheme due to Cleve Moler and Donald Morrison [IBM Journal of + Research and Development 27 (1983), 577-581]. It modifies a and b in such a + way that their Pythagorean sum remains invariant, while the smaller + argument decreases.""" + return sqrt(a*a + b*b) +# >>> +def mp_curl_ratio(gamma, a_tension, b_tension): # <<< + """The curl ratio subroutine has three arguments, which our previous + notation encourages us to call gamma, 1/alpha, and 1/beta. It is a somewhat + tedious program to calculate + [(3-alpha)alpha^2 gamma + beta^3] / [alpha^3 gamma + (3-beta)beta^2], + with the result reduced to 4 if it exceeds 4. (This reduction of curl is + necessary only if the curl and tension are both large.) The values of alpha + and beta will be at most 4/3. + + See mp.pdf (items 365, 366).""" + alpha = 1.0/a_tension + beta = 1.0/b_tension + return min(4.0, ((3.0-alpha)*alpha**2*gamma + beta**3) / + (alpha**3*gamma + (3.0-beta)*beta**2)) +# >>> +def mp_ab_vs_cd(a, b, c, d): # <<< + """Tests rigorously if ab is greater than, equal to, or less than cd, given + integers (a, b, c, d). In most cases a quick decision is reached. The + result is +1, 0, or -1 in the three respective cases. + See mpmath.pdf (item 33).""" + if a*b == c*d: + return 0 + if a*b > c*d: + return 1 + return -1 +# >>> +def mp_velocity(st, ct, sf, cf, t): # <<< + """Metapost's standard velocity subroutine for cubic Bezier curves. + See mpmath.pdf (item 30) and mp.pdf (item 339).""" + return min(4.0, (2.0 + sqrt(2)*(st-sf/16.0)*(sf-st/16.0)*(ct-cf)) / + (1.5*t*(2+(sqrt(5)-1)*ct + (3-sqrt(5))*cf))) +# >>> +def reduce_angle(A): # <<< + """A macro in mp.c""" + if abs(A) > one_eighty_deg: + if A > 0: + A -= three_sixty_deg + else: + A += three_sixty_deg + return A +# >>> + +# vim:foldmethod=marker:foldmarker=<<<,>>> diff -Nru pyx-0.11.1/pyx/metapost/path.py pyx-0.12.1/pyx/metapost/path.py --- pyx-0.11.1/pyx/metapost/path.py 1970-01-01 00:00:00.000000000 +0000 +++ pyx-0.12.1/pyx/metapost/path.py 2012-10-17 15:31:57.000000000 +0000 @@ -0,0 +1,356 @@ +# -*- coding: ISO-8859-1 -*- +# +# Copyright (C) 2011 Michael Schindler +# +# This file is part of PyX (http://pyx.sourceforge.net/). +# +# PyX is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# PyX is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with PyX; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +from math import atan2, radians +from pyx import unit, attr, normpath +from pyx import path as pathmodule + +from mp_path import mp_endpoint, mp_explicit, mp_given, mp_curl, mp_open, mp_end_cycle, mp_make_choices + +# global epsilon (default precision length of metapost, in pt) +_epsilon = 1e-5 + +def set(epsilon=None): + global _epsilon + if epsilon is not None: + _epsilon = epsilon + +################################################################################ +# Path knots +################################################################################ + +class _knot: + + """Internal knot as used in MetaPost (mp.c)""" + + def __init__(self, x_pt, y_pt, ltype, lx_pt, ly_pt, rtype, rx_pt, ry_pt): + self.x_pt = x_pt + self.y_pt = y_pt + self.ltype = ltype + self.lx_pt = lx_pt + self.ly_pt = ly_pt + self.rtype = rtype + self.rx_pt = rx_pt + self.ry_pt = ry_pt + # this is a linked list: + self.next = self + + def set_left_tension(self, tens): + self.ly_pt = tens + def set_right_tension(self, tens): + self.ry_pt = tens + def set_left_curl(self, curl): + self.lx_pt = curl + def set_right_curl(self, curl): + self.rx_pt = curl + set_left_given = set_left_curl + set_right_given = set_right_curl + + def left_tension(self): + return self.ly_pt + def right_tension(self): + return self.ry_pt + def left_curl(self): + return self.lx_pt + def right_curl(self): + return self.rx_pt + left_given = left_curl + right_given = right_curl + + def linked_len(self): + """returns the length of a circularly linked list of knots""" + n = 1 + p = self.next + while not p is self: + n += 1 + p = p.next + return n + + def __repr__(self): + result = "" + # left + if self.ltype == mp_endpoint: + pass + elif self.ltype == mp_explicit: + result += "{explicit %s %s}" % (self.lx_pt, self.ly_pt) + elif self.ltype == mp_given: + result += "{given %g tens %g}" % (self.lx_pt, self.ly_pt) + elif self.ltype == mp_curl: + result += "{curl %g tens %g}" % (self.lx_pt, self.ly_pt) + elif self.ltype == mp_open: + result += "{open tens %g}" % (self.ly_pt) + elif self.ltype == mp_end_cycle: + result += "{cycle tens %g}" % (self.ly_pt) + result += "(%g %g)" % (self.x_pt, self.y_pt) + # right + if self.rtype == mp_endpoint: + pass + elif self.rtype == mp_explicit: + result += "{explicit %g %g}" % (self.rx_pt, self.ry_pt) + elif self.rtype == mp_given: + result += "{given %g tens %g}" % (self.rx_pt, self.ry_pt) + elif self.rtype == mp_curl: + result += "{curl %g tens %g}" % (self.rx_pt, self.ry_pt) + elif self.rtype == mp_open: + result += "{open tens %g}" % (self.ry_pt) + elif self.rtype == mp_end_cycle: + result += "{cycle tens %g}" % (self.ry_pt) + return result + +class beginknot_pt(_knot): + + """A knot which interrupts a path, or which allows to continue it with a straight line""" + + def __init__(self, x_pt, y_pt, curl=1, angle=None): + if angle is None: + type, value = mp_curl, curl + else: + type, value = mp_given, angle + # tensions are modified by the adjacent curve, but default is 1 + _knot.__init__(self, x_pt, y_pt, mp_endpoint, None, None, type, value, 1) + +class beginknot(beginknot_pt): + + def __init__(self, x, y, curl=1, angle=None): + if not (angle is None): + angle = radians(angle) + beginknot_pt.__init__(self, unit.topt(x), unit.topt(y), curl, angle) + +startknot = beginknot + +class endknot_pt(_knot): + + """A knot which interrupts a path, or which allows to continue it with a straight line""" + + def __init__(self, x_pt, y_pt, curl=1, angle=None): + if angle is None: + type, value = mp_curl, curl + else: + type, value = mp_given, angle + # tensions are modified by the adjacent curve, but default is 1 + _knot.__init__(self, x_pt, y_pt, type, value, 1, mp_endpoint, None, None) + +class endknot(endknot_pt): + + def __init__(self, x, y, curl=1, angle=None): + if not (angle is None): + angle = radians(angle) + endknot_pt.__init__(self, unit.topt(x), unit.topt(y), curl, angle) + +class smoothknot_pt(_knot): + + """A knot with continous tangent and "mock" curvature.""" + + def __init__(self, x_pt, y_pt): + # tensions are modified by the adjacent curve, but default is 1 + _knot.__init__(self, x_pt, y_pt, mp_open, None, 1, mp_open, None, 1) + +class smoothknot(smoothknot_pt): + + def __init__(self, x, y): + smoothknot_pt.__init__(self, unit.topt(x), unit.topt(y)) + +knot = smoothknot + +class roughknot_pt(_knot): + + """A knot with noncontinous tangent.""" + + def __init__(self, x_pt, y_pt, lcurl=1, rcurl=None, langle=None, rangle=None): + """Specify either the relative curvatures, or tangent angles left (l) + or right (r) of the point.""" + if langle is None: + ltype, lvalue = mp_curl, lcurl + else: + ltype, lvalue = mp_given, langle + if rcurl is not None: + rtype, rvalue = mp_curl, rcurl + elif rangle is not None: + rtype, rvalue = mp_given, rangle + else: + rtype, rvalue = ltype, lvalue + # tensions are modified by the adjacent curve, but default is 1 + _knot.__init__(self, x_pt, y_pt, ltype, lvalue, 1, rtype, rvalue, 1) + +class roughknot(roughknot_pt): + + def __init__(self, x, y, lcurl=1, rcurl=None, langle=None, rangle=None): + if langle is not None: + langle = radians(langle) + if rangle is not None: + rangle = radians(rangle) + roughknot_pt.__init__(self, unit.topt(x), unit.topt(y), lcurl, rcurl, langle, rangle) + +################################################################################ +# Path links +################################################################################ + +class _link: + def set_knots(self, left_knot, right_knot): + """Sets the internal properties of the metapost knots""" + pass + +class line(_link): + + """A straight line""" + + def __init__(self, keepangles=False): + """The option keepangles will guarantee a continuous tangent. The + curvature may become discontinuous, however""" + self.keepangles = keepangles + + def set_knots(self, left_knot, right_knot): + left_knot.rtype = mp_endpoint + right_knot.ltype = mp_endpoint + left_knot.rx_pt, left_knot.ry_pt = None, None + right_knot.lx_pt, right_knot.ly_pt = None, None + if self.keepangles: + angle = atan2(right_knot.y_pt-left_knot.y_pt, right_knot.x_pt-left_knot.x_pt) + left_knot.ltype = mp_given + left_knot.set_left_given(angle) + right_knot.rtype = mp_given + right_knot.set_right_given(angle) + + +class controlcurve_pt(_link): + + """A cubic Bezier curve which has its control points explicity set""" + + def __init__(self, lcontrol_pt, rcontrol_pt): + """The control points at the beginning (l) and the end (r) must be + coordinate pairs""" + self.lcontrol_pt = lcontrol_pt + self.rcontrol_pt = rcontrol_pt + + def set_knots(self, left_knot, right_knot): + left_knot.rtype = mp_explicit + right_knot.ltype = mp_explicit + left_knot.rx_pt, left_knot.ry_pt = self.lcontrol_pt + right_knot.lx_pt, right_knot.ly_pt = self.rcontrol_pt + +class controlcurve(controlcurve_pt): + + def __init__(self, lcontrol, rcontrol): + controlcurve_pt.__init__(self, (unit.topt(lcontrol[0]), unit.topt(lcontrol[1])), + (unit.topt(rcontrol[0]), unit.topt(rcontrol[1]))) + + +class tensioncurve(_link): + + """A yet unspecified cubic Bezier curve""" + + def __init__(self, ltension=1, latleast=False, rtension=None, ratleast=None): + """The tension parameters indicate the tensions at the beginning (l) + and the end (r) of the curve. Set the parameters (l/r)atleast to True + if you want to avoid inflection points.""" + if rtension is None: + rtension = ltension + if ratleast is None: + ratleast = latleast + # make sure that tension >= 0.75 (p. 9 mpman.pdf) + self.ltension = max(0.75, abs(ltension)) + self.rtension = max(0.75, abs(rtension)) + if latleast: + self.ltension = -self.ltension + if ratleast: + self.rtension = -self.rtension + + def set_knots(self, left_knot, right_knot): + if left_knot.rtype <= mp_explicit or right_knot.ltype <= mp_explicit: + raise Exception("metapost curve with given tension cannot have explicit knots") + left_knot.set_right_tension(self.ltension) + right_knot.set_left_tension(self.rtension) + +curve = tensioncurve + + +################################################################################ +# Path creation class +################################################################################ + +class path(pathmodule.path): + + """A MetaPost-like path, which finds an optimal way through given points. + + At points, you can either specify a given tangent direction (angle in + degrees) or a certain "curlyness" (relative to the curvature at the other + end of a curve), or nothing. In the latter case, both the tangent and the + "mock" curvature (an approximation to the real curvature, introduced by + J.D. Hobby in MetaPost) will be continuous. + + The shape of the cubic Bezier curves between two points is controlled by + its "tension", unless you choose to set the control points manually.""" + + def __init__(self, elems, epsilon=None): + """elems should contain metapost knots or links""" + if epsilon is None: + epsilon = _epsilon + knots = [] + is_closed = True + for i, elem in enumerate(elems): + if isinstance(elem, _link): + elem.set_knots(elems[i-1], elems[(i+1)%len(elems)]) + elif isinstance(elem, _knot): + knots.append(elem) + if elem.ltype == mp_endpoint or elem.rtype == mp_endpoint: + is_closed = False + + # link the knots among each other + for i in range(len(knots)): + knots[i-1].next = knots[i] + + # determine the control points + mp_make_choices(knots[0], epsilon) + + pathmodule.path.__init__(self) + # build up the path + do_moveto = True + do_lineto = False + do_curveto = False + prev = None + for i, elem in enumerate(elems): + if isinstance(elem, _link): + do_moveto = False + if isinstance(elem, line): + do_lineto, do_curveto = True, False + else: + do_lineto, do_curveto = False, True + elif isinstance(elem, _knot): + if do_moveto: + self.append(pathmodule.moveto_pt(elem.x_pt, elem.y_pt)) + if do_lineto: + self.append(pathmodule.lineto_pt(elem.x_pt, elem.y_pt)) + elif do_curveto: + self.append(pathmodule.curveto_pt(prev.rx_pt, prev.ry_pt, elem.lx_pt, elem.ly_pt, elem.x_pt, elem.y_pt)) + do_moveto = True + do_lineto = False + do_curveto = False + prev = elem + + # close the path if necessary + if knots[0].ltype == mp_explicit: + elem = knots[0] + if do_lineto and is_closed: + self.append(pathmodule.closepath()) + elif do_curveto: + self.append(pathmodule.curveto_pt(prev.rx_pt, prev.ry_pt, elem.lx_pt, elem.ly_pt, elem.x_pt, elem.y_pt)) + if is_closed: + self.append(pathmodule.closepath()) + diff -Nru pyx-0.11.1/pyx/normpath.py pyx-0.12.1/pyx/normpath.py --- pyx-0.11.1/pyx/normpath.py 2011-05-18 09:09:35.000000000 +0000 +++ pyx-0.12.1/pyx/normpath.py 2012-10-17 15:31:57.000000000 +0000 @@ -510,8 +510,8 @@ return result def intersect(self, other, epsilon): - # There can be no intersection point, when the control boxes are not - # overlapping. Note that we use the control box instead of the bounding + # There can be no intersection point if the control boxes do not + # overlap. Note that we use the control box instead of the bounding # box here, because the former can be calculated more efficiently for # Bezier curves. if not self.cbox().intersects(other.cbox()): diff -Nru pyx-0.11.1/pyx/path.py pyx-0.12.1/pyx/path.py --- pyx-0.11.1/pyx/path.py 2011-05-18 09:09:35.000000000 +0000 +++ pyx-0.12.1/pyx/path.py 2012-10-17 15:31:57.000000000 +0000 @@ -900,11 +900,11 @@ def updatebbox(self, bbox, context): for point_pt in self.points_pt: - bbox.includepoint_pt(*point_pt[0: 2]) - bbox.includepoint_pt(*point_pt[2: 4]) - bbox.includepoint_pt(*point_pt[4: 6]) - if self.points_pt: - context.x_pt, context.y_pt = self.points_pt[-1][4:] + xmin_pt, xmax_pt = _bezierpolyrange(context.x_pt, point_pt[0], point_pt[2], point_pt[4]) + ymin_pt, ymax_pt = _bezierpolyrange(context.y_pt, point_pt[1], point_pt[3], point_pt[5]) + bbox.includepoint_pt(xmin_pt, ymin_pt) + bbox.includepoint_pt(xmax_pt, ymax_pt) + context.x_pt, context.y_pt = point_pt[4:] def updatenormpath(self, normpath, context): x0_pt, y0_pt = context.x_pt, context.y_pt diff -Nru pyx-0.11.1/pyx/pattern.py pyx-0.12.1/pyx/pattern.py --- pyx-0.11.1/pyx/pattern.py 2011-05-18 09:09:35.000000000 +0000 +++ pyx-0.12.1/pyx/pattern.py 2012-10-17 15:31:57.000000000 +0000 @@ -28,11 +28,11 @@ # TODO: pattern should not derive from canvas but wrap a canvas -class pattern(canvas._canvas, attr.exclusiveattr, style.fillstyle): +class pattern(canvas.canvas, attr.exclusiveattr, style.fillstyle): def __init__(self, painttype=1, tilingtype=1, xstep=None, ystep=None, bbox=None, trafo=None, bboxenlarge=5*unit.t_pt, **kwargs): - canvas._canvas.__init__(self, **kwargs) + canvas.canvas.__init__(self, **kwargs) attr.exclusiveattr.__init__(self, pattern) self.id = "pattern%d" % id(self) self.patterntype = 1 @@ -73,7 +73,7 @@ # process pattern, letting it register its resources and calculate the bbox of the pattern patternfile = cStringIO.StringIO() realpatternbbox = bboxmodule.empty() - canvas._canvas.processPS(self, patternfile, writer, pswriter.context(), registry, realpatternbbox) + canvas.canvas.processPS(self, patternfile, writer, pswriter.context(), registry, realpatternbbox) patternproc = patternfile.getvalue() patternfile.close() @@ -114,7 +114,7 @@ patternfile = cStringIO.StringIO() realpatternbbox = bboxmodule.empty() - canvas._canvas.processPDF(self, patternfile, writer, pdfwriter.context(), patternregistry, realpatternbbox) + canvas.canvas.processPDF(self, patternfile, writer, pdfwriter.context(), patternregistry, realpatternbbox) patternproc = patternfile.getvalue() patternfile.close() diff -Nru pyx-0.11.1/pyx/pycompat.py pyx-0.12.1/pyx/pycompat.py --- pyx-0.11.1/pyx/pycompat.py 2011-05-19 11:11:25.000000000 +0000 +++ pyx-0.12.1/pyx/pycompat.py 2012-10-17 15:31:57.000000000 +0000 @@ -1,8 +1,8 @@ # -*- encoding: utf-8 -*- # # -# Copyright (C) 2011 Jörg Lehmann -# Copyright (C) 2011 André Wobst +# Copyright (C) 2011-2012 Jörg Lehmann +# Copyright (C) 2011-2012 André Wobst # # This file is part of PyX (http://pyx.sourceforge.net/). # @@ -22,6 +22,21 @@ class _marker: pass + +class wait_pipe: + + def __init__(self, pipe, wait): + self.pipe = pipe + self.wait = wait + + def write(self, str): + self.pipe.write(str) + + def close(self): + self.pipe.close() + self.wait() + + def popen(cmd, mode="r", bufsize=_marker): try: import subprocess @@ -37,7 +52,7 @@ if mode[0] == "r": return pipes.stdout else: - return pipes.stdin + return wait_pipe(pipes.stdin, pipes.wait) except ImportError: import os if bufsize is _marker: @@ -45,6 +60,39 @@ else: return os.popen(cmd, mode, bufsize) +def popen2(cmd, mode="t", bufsize=_marker): + try: + import subprocess + kwargs = {"stdin": subprocess.PIPE, + "stdout": subprocess.PIPE} + if bufsize is not _marker: + kwargs["bufsize"] = bufsize + pipes = subprocess.Popen(cmd, shell=True, **kwargs) + return pipes.stdin, pipes.stdout + except ImportError: + import os + if bufsize is _marker: + return os.popen2(cmd, mode) + else: + return os.popen2(cmd, mode, bufsize) + +def popen4(cmd, mode="t", bufsize=_marker): + try: + import subprocess + kwargs = {"stdin": subprocess.PIPE, + "stdout": subprocess.PIPE, + "stderr": subprocess.STDOUT} + if bufsize is not _marker: + kwargs["bufsize"] = bufsize + pipes = subprocess.Popen(cmd, shell=True, **kwargs) + return pipes.stdin, pipes.stdout + except ImportError: + import os + if bufsize is _marker: + return os.popen4(cmd, mode) + else: + return os.popen4(cmd, mode, bufsize) + try: any = any except NameError: @@ -59,3 +107,12 @@ except NameError: # Python 2.3 from sets import Set as set + +try: + sorted = sorted +except NameError: + # Python 2.3 + def sorted(l): + l = list(l) + l.sort() + return l diff -Nru pyx-0.11.1/pyx/pykpathsea.c pyx-0.12.1/pyx/pykpathsea.c --- pyx-0.11.1/pyx/pykpathsea.c 2011-05-18 09:09:35.000000000 +0000 +++ pyx-0.12.1/pyx/pykpathsea.c 2012-10-17 15:31:57.000000000 +0000 @@ -56,7 +56,7 @@ /* if (!strcmp(format, "otp")) kpse_file_format = kpse_otp_format; else */ /* if (!strcmp(format, "ovf")) kpse_file_format = kpse_ovf_format; else */ /* if (!strcmp(format, "ovp")) kpse_file_format = kpse_ovp_format; else */ - if (!strcmp(format, "graphics/figure")) kpse_file_format = kpse_pict_format; else + if (!strcmp(format, "graphic/figure")) kpse_file_format = kpse_pict_format; else /* if (!strcmp(format, "tex")) kpse_file_format = kpse_tex_format; else */ /* if (!strcmp(format, "TeX system documentation")) kpse_file_format = kpse_texdoc_format; else */ /* if (!strcmp(format, "texpool")) kpse_file_format = kpse_texpool_format; else */ diff -Nru pyx-0.11.1/pyx/style.py pyx-0.12.1/pyx/style.py --- pyx-0.11.1/pyx/style.py 2011-05-18 09:09:35.000000000 +0000 +++ pyx-0.12.1/pyx/style.py 2012-10-17 15:31:57.000000000 +0000 @@ -1,9 +1,9 @@ # -*- encoding: utf-8 -*- # # -# Copyright (C) 2002-2011 Jörg Lehmann +# Copyright (C) 2002-2012 Jörg Lehmann # Copyright (C) 2003-2006 Michael Schindler -# Copyright (C) 2002-2011 André Wobst +# Copyright (C) 2002-2012 André Wobst # # This file is part of PyX (http://pyx.sourceforge.net/). # @@ -31,10 +31,12 @@ class strokestyle(canvasitem.canvasitem): def bbox(self): + # TODO: see TODO in bbox method of canvasitem return bboxmodule.empty() class fillstyle(canvasitem.canvasitem): def bbox(self): + # TODO: see TODO in bbox method of canvasitem return bboxmodule.empty() # diff -Nru pyx-0.11.1/pyx/text.py pyx-0.12.1/pyx/text.py --- pyx-0.11.1/pyx/text.py 2011-05-18 09:09:35.000000000 +0000 +++ pyx-0.12.1/pyx/text.py 2012-10-17 15:31:57.000000000 +0000 @@ -22,17 +22,10 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA import errno, glob, os, threading, Queue, re, tempfile, atexit, time, warnings -import config, unit, box, canvas, trafo, version, attr, style, filelocator +import config, unit, box, canvas, trafo, version, attr, style, filelocator, pycompat, path from pyx.dvi import dvifile import bbox as bboxmodule -try: - import subprocess -except ImportError: - have_subprocess = False -else: - have_subprocess = True - class PyXTeXWarning(UserWarning): pass warnings.filterwarnings('always', category=PyXTeXWarning) @@ -671,7 +664,7 @@ self.quitevent.set() -class textbox(box.rect, canvas._canvas): +class textbox(box.rect, canvas.canvas): """basically a box.rect, but it contains a text created by the texrunner - texrunner._text and texrunner.text return such an object - _textbox instances can be inserted into a canvas @@ -690,7 +683,7 @@ self.depth = depth self.texttrafo = trafo.scale(unit.scale["x"]).translated(x, y) box.rect.__init__(self, x - left, y - depth, left + right, depth + height, abscenter = (left, depth)) - canvas._canvas.__init__(self, attrs) + canvas.canvas.__init__(self, attrs) self.finishdvi = finishdvi self.dvicanvas = None self.insertdvicanvas = 0 @@ -719,16 +712,27 @@ self.ensuredvicanvas() return self.texttrafo.apply(*self.dvicanvas.markers[marker]) + def textpath(self): + self.ensuredvicanvas() + textpath = path.path() + for item in self.dvicanvas.items: + try: + textpath += item.textpath() + except AttributeError: + # ignore color settings etc. + pass + return textpath.transformed(self.texttrafo) + def processPS(self, file, writer, context, registry, bbox): self.ensuredvicanvas() abbox = bboxmodule.empty() - canvas._canvas.processPS(self, file, writer, context, registry, abbox) + canvas.canvas.processPS(self, file, writer, context, registry, abbox) bbox += box.rect.bbox(self) def processPDF(self, file, writer, context, registry, bbox): self.ensuredvicanvas() abbox = bboxmodule.empty() - canvas._canvas.processPDF(self, file, writer, context, registry, abbox) + canvas.canvas.processPDF(self, file, writer, context, registry, abbox) bbox += box.rect.bbox(self) @@ -905,16 +909,11 @@ ipcflag = " --ipc" else: ipcflag = "" - if have_subprocess: - p = subprocess.Popen("%s%s %s" % (self.mode, ipcflag, self.texfilename), shell=True, bufsize=0, - stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True) - self.texinput, self.texoutput = p.stdin, p.stdout - else: - try: - self.texinput, self.texoutput = os.popen4("%s%s %s" % (self.mode, ipcflag, self.texfilename), "t", 0) - except ValueError: - # XXX: workaround for MS Windows (bufsize = 0 makes trouble!?) - self.texinput, self.texoutput = os.popen4("%s%s %s" % (self.mode, ipcflag, self.texfilename), "t") + try: + self.texinput, self.texoutput = pycompat.popen4("%s%s %s" % (self.mode, ipcflag, self.texfilename), "t", 0) + except ValueError: + # workaround: bufsize = 0 is not supported on MS windows for os.open4 (Python 2.4 and below, i.e. where subprocess is not available) + self.texinput, self.texoutput = pycompat.popen4("%s%s %s" % (self.mode, ipcflag, self.texfilename), "t") atexit.register(_cleantmp, self) self.expectqueue = Queue.Queue(1) # allow for a single entry only -> keeps the next InputMarker to be wait for self.gotevent = threading.Event() # keeps the got inputmarker event @@ -964,7 +963,7 @@ elif self.mode == "latex": if self.pyxgraphics: pyxdef = filelocator.open("pyx.def", [], "rb") - pyxdef_filename = self.texfilename + ".pyxdef" + pyxdef_filename = self.texfilename + ".pyx.def" pyxdef_file = open(pyxdef_filename, "wb") pyxdef_file.write(pyxdef.read()) pyxdef.close() @@ -1046,7 +1045,7 @@ self.dvifile = dvifile.DVIfile(dvifilename, debug=self.dvidebug) page = 1 for box in self.needdvitextboxes: - box.setdvicanvas(self.dvifile.readpage([ord("P"), ord("y"), ord("X"), page, 0, 0, 0, 0, 0, 0], fontmap=box.fontmap)) + box.setdvicanvas(self.dvifile.readpage([ord("P"), ord("y"), ord("X"), page, 0, 0, 0, 0, 0, 0], fontmap=box.fontmap, singlecharmode=box.singlecharmode)) page += 1 if not ignoretail and self.dvifile.readpage(None) is not None: raise RuntimeError("end of dvifile expected") @@ -1163,7 +1162,7 @@ PyXBoxPattern = re.compile(r"PyXBox:page=(?P\d+),lt=(?P-?\d*((\d\.?)|(\.?\d))\d*)pt,rt=(?P-?\d*((\d\.?)|(\.?\d))\d*)pt,ht=(?P-?\d*((\d\.?)|(\.?\d))\d*)pt,dp=(?P-?\d*((\d\.?)|(\.?\d))\d*)pt:") - def text(self, x, y, expr, textattrs=[], texmessages=[], fontmap=None): + def text(self, x, y, expr, textattrs=[], texmessages=[], fontmap=None, singlecharmode=False): """create text by passing expr to TeX/LaTeX - returns a textbox containing the result from running expr thru TeX/LaTeX - the box center is set to x, y @@ -1213,9 +1212,10 @@ # this is quite different from what we do elsewhere!!! # see https://sourceforge.net/mailarchive/forum.php?thread_id=9137692&forum_id=23700 if self.texipc: - box.setdvicanvas(self.dvifile.readpage([ord("P"), ord("y"), ord("X"), self.page, 0, 0, 0, 0, 0, 0], fontmap=fontmap)) + box.setdvicanvas(self.dvifile.readpage([ord("P"), ord("y"), ord("X"), self.page, 0, 0, 0, 0, 0, 0], fontmap=fontmap, singlecharmode=singlecharmode)) else: box.fontmap = fontmap + box.singlecharmode = singlecharmode self.needdvitextboxes.append(box) return box diff -Nru pyx-0.11.1/pyx/unit.py pyx-0.12.1/pyx/unit.py --- pyx-0.11.1/pyx/unit.py 2011-05-18 09:09:35.000000000 +0000 +++ pyx-0.12.1/pyx/unit.py 2012-10-17 15:31:57.000000000 +0000 @@ -22,39 +22,37 @@ import types -scale = { 't':1, 'u':1, 'v':1, 'w':1, 'x':1 } +scale = dict(u=1, v=1, w=1, x=1) _default_unit = "cm" -_m = { - 'm' : 1, - 'cm': 0.01, - 'mm': 0.001, - 'inch': 0.01*2.54, - 'pt': 0.01*2.54/72, +_m = { + "m": 1, + "cm": 0.01, + "mm": 0.001, + "inch": 0.01*2.54, + "pt": 0.01*2.54/72, } def set(uscale=None, vscale=None, wscale=None, xscale=None, defaultunit=None): if uscale is not None: - scale['u'] = uscale + scale["u"] = uscale if vscale is not None: - scale['v'] = vscale + scale["v"] = vscale if wscale is not None: - scale['w'] = wscale + scale["w"] = wscale if xscale is not None: - scale['x'] = xscale + scale["x"] = xscale if defaultunit is not None: global _default_unit _default_unit = defaultunit def _convert_to(l, dest_unit="m"): - if type(l) in (types.IntType, types.LongType, types.FloatType): - return l * _m[_default_unit] * scale['u'] / _m[dest_unit] - elif not isinstance(l, length): - l = length(l) # convert to length instance if necessary - - return (l.t + l.u*scale['u'] + l.v*scale['v'] + l.w*scale['w'] + l.x*scale['x']) / _m[dest_unit] + if isinstance(l, length): + return (l.t + l.u*scale["u"] + l.v*scale["v"] + l.w*scale["w"] + l.x*scale["x"]) / _m[dest_unit] + else: + return l * _m[_default_unit] * scale["u"] / _m[dest_unit] def tom(l): return _convert_to(l, "m") diff -Nru pyx-0.11.1/pyx/version.py pyx-0.12.1/pyx/version.py --- pyx-0.11.1/pyx/version.py 2011-05-20 13:01:13.000000000 +0000 +++ pyx-0.12.1/pyx/version.py 2012-10-26 08:20:28.000000000 +0000 @@ -1,8 +1,9 @@ # -*- encoding: utf-8 -*- # # -# Copyright (C) 2002-2011 Jörg Lehmann -# Copyright (C) 2002-2011 André Wobst +# Copyright (C) 2002-2012 Jörg Lehmann +# Copyright (C) 2002-2012 André Wobst +# Copyright (C) 2011 Michael Schindler # # This file is part of PyX (http://pyx.sourceforge.net/). # @@ -21,5 +22,5 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA -version = "0.11.1" -date = "2011/05/20" +version = "0.12.1" +date = "2012/10/26" diff -Nru pyx-0.11.1/setup.py pyx-0.12.1/setup.py --- pyx-0.11.1/setup.py 2011-05-20 13:01:14.000000000 +0000 +++ pyx-0.12.1/setup.py 2012-10-16 20:56:15.000000000 +0000 @@ -56,7 +56,7 @@ description=description, long_description=long_description, license="GPL", - packages=["pyx", "pyx/graph", "pyx/graph/axis", "pyx/font", "pyx/dvi"], + packages=["pyx", "pyx/graph", "pyx/graph/axis", "pyx/font", "pyx/dvi", "pyx/metapost"], package_data={"pyx": ["data/afm/*", "data/lfs/*", "data/def/*", "data/pyxrc"]}, ext_modules=ext_modules, classifiers=["Development Status :: 3 - Alpha",