Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating publication-quality geometric figures in Python

I am a mathematician. Recently, I became the editor of the puzzles and problems column for a well-known magazine. Occasionally, I need to create a figure to accompany a problem or solution. These figures mostly relate to 2D (occasionally, 3D) euclidean geometry (lines, polygons, circles, plus the occasional ellipse or other conic section). The goal is obtaining figures of very high quality (press-ready), with Computer Modern ("TeX") textual labels. My hope is finding (or perhaps helping write!) a relatively high-level Python library that "knows" euclidean geometry in the sense that natural operations (e.g., drawing a perpendicular line to a given one passing through a given point, bisecting a given angle, or reflecting a figure A on a line L to obtain a new figure A') are already defined in the library. Of course, the ability to create figures after their elements are defined is a crucial goal (e.g., as Encapsulated Postscript).

I know multiple sub-optimal solutions to this problem (some partial), but I don't know of any that is both simple and flexible. Let me explain:

  • Asymptote (similar to/based on Metapost) allows creating extremely high-quality figures of great complexity, but knows almost nothing about geometric constructions (it is a rather low-level language) and thus any nontrivial construction requires quite a long script.
  • TikZ with package tkz-euclide is high-level, flexible and also generates quality figures, but its syntax is so heavy that I just cry for Python's simplicity in comparison. (Some programs actually export to TikZ---see below.)
  • Dynamic Geometry programs, of which I'm most familiar with Geogebra, often have figure-exporting features (EPS, TikZ, etc.), but are meant to be used interactively. Sometimes, what one needs is a figure based on hard specs (e.g., exact side lengths)---defining objects in a script is ultimately more flexible (if correspondingly less convenient).
  • Two programs, Eukleides and GCLC, are closest to what I'm looking for: They generate figures (EPS format; GCLC also exports to TikZ). Eukleides has the prettiest, simplest syntax of all the options (see the examples), but it happens to be written in C (with source available, though I'm not sure about the license), rather limited/non-customizable, and no longer maintained. GCLC is still maintained but it is closed-source, its syntax is significantly worse than Eukleides's, and has certain other unnatural quirks. Besides, it is not available for Mac OS (my laptop is a Mac).

Python has:

  • Matplotlib, which produces extremely high-quality figures (particularly of functions or numerical data), but does not seem to know about geometric constructions, and
  • Sympy has a geometry module which does know about geometric objects and constructions, all accessible in delightful Python syntax, but seems to have no figure-exporting (or even displaying?) capabilities.

Finally, a question: Is there a library, something like "Figures for Sympy/geometry", that uses Python syntax to describe geometric objects and constructions, allowing to generate high-quality figures (primarily for printing, say EPS)?

If a library with such functionality does not exist, I would consider helping to write one (perhaps an extension to Sympy?). I will appreciate pointers.

like image 514
Eduardo Dueñez Avatar asked Feb 04 '16 19:02

Eduardo Dueñez


1 Answers

There is a way to generate vector images with matplotlob, outputting with the library io to a vector image (SVG) with this approach.

I personally tried to run the code of the approach (generate a vectorial histogram) in that webpage as a python file, and it worked.

The code:

    import numpy as np     import matplotlib.pyplot as plt     import xml.etree.ElementTree as ET     from io import BytesIO     import json     plt.rcParams['svg.fonttype'] = 'none'     # Apparently, this `register_namespace` method is necessary to avoid garbling     # the XML namespace with ns0.     ET.register_namespace("", "http://www.w3.org/2000/svg")     # Fixing random state for reproducibility     np.random.seed(19680801)     # --- Create histogram, legend and title ---     plt.figure()     r = np.random.randn(100)     r1 = r + 1     labels = ['Rabbits', 'Frogs']     H = plt.hist([r, r1], label=labels)     containers = H[-1]     leg = plt.legend(frameon=False)     plt.title("From a web browser, click on the legend\n"             "marker to toggle the corresponding histogram.")     # --- Add ids to the svg objects we'll modify     hist_patches = {}     for ic, c in enumerate(containers):         hist_patches['hist_%d' % ic] = []         for il, element in enumerate(c):             element.set_gid('hist_%d_patch_%d' % (ic, il))             hist_patches['hist_%d' % ic].append('hist_%d_patch_%d' % (ic, il))     # Set ids for the legend patches     for i, t in enumerate(leg.get_patches()):         t.set_gid('leg_patch_%d' % i)     # Set ids for the text patches     for i, t in enumerate(leg.get_texts()):         t.set_gid('leg_text_%d' % i)     # Save SVG in a fake file object.     f = BytesIO()     plt.savefig(f, format="svg")     # Create XML tree from the SVG file.     tree, xmlid = ET.XMLID(f.getvalue())     # --- Add interactivity ---     # Add attributes to the patch objects.     for i, t in enumerate(leg.get_patches()):         el = xmlid['leg_patch_%d' % i]         el.set('cursor', 'pointer')         el.set('onclick', "toggle_hist(this)")     # Add attributes to the text objects.     for i, t in enumerate(leg.get_texts()):         el = xmlid['leg_text_%d' % i]         el.set('cursor', 'pointer')         el.set('onclick', "toggle_hist(this)")     # Create script defining the function `toggle_hist`.     # We create a global variable `container` that stores the patches id     # belonging to each histogram. Then a function "toggle_element" sets the     # visibility attribute of all patches of each histogram and the opacity     # of the marker itself.     script = """     <script type="text/ecmascript">     <![CDATA[     var container = %s     function toggle(oid, attribute, values) {         /* Toggle the style attribute of an object between two values.         Parameters         ----------         oid : str         Object identifier.         attribute : str         Name of style attribute.         values : [on state, off state]         The two values that are switched between.         */         var obj = document.getElementById(oid);         var a = obj.style[attribute];         a = (a == values[0] || a == "") ? values[1] : values[0];         obj.style[attribute] = a;         }     function toggle_hist(obj) {         var num = obj.id.slice(-1);         toggle('leg_patch_' + num, 'opacity', [1, 0.3]);         toggle('leg_text_' + num, 'opacity', [1, 0.5]);          var names = container['hist_'+num]          for (var i=0; i < names.length; i++) {             toggle(names[i], 'opacity', [1, 0])         };         }     ]]>     </script>     """ % json.dumps(hist_patches)     # Add a transition effect     css = tree.getchildren()[0][0]     css.text = css.text + "g {-webkit-transition:opacity 0.4s ease-out;" + \         "-moz-transition:opacity 0.4s ease-out;}"     # Insert the script and save to file.     tree.insert(0, ET.XML(script))     ET.ElementTree(tree).write("svg_histogram.svg") 

Previously, you need to pip install the required libraries on the top lines, and it successfully saved a SVG file with a plot (you can read the file and zoomwant in the histogram and you will get no pixels, as the image is generated with mathematicals functions).

It (obviously for our time) uses python 3.

You then could import the SVG image within your TeX document for the publication rendering.

I hope it may help.

Greetings, Javier.

like image 137
Javier González Berenguel Avatar answered Sep 21 '22 14:09

Javier González Berenguel