While taking an axiomatic geometry course in parallel with learning about Sage this quarter, I was drawn to the following question: Can I produce the type of diagrams which I need to draw for my geometry assignments using Sage? As it turns out, Sage includes in its drawing packages all of the basic functionality needed, but provides no convenient ways to construct geometric objects like points with a label, line segments with marked endpoints, or triangles. Throughout the quarter, I wrote some code in Sage which began to implement my desired behavior, and decided to expand that work into a decent proof of concept for a more complete geometry package in Sage.
The intended scope of the project was to allow for the easy (one line of code) construction of labeled points, line segments, and triangles. Furthermore, I wanted my line segment objects to be able to draw tick marks along their length to indicate congruence with another segment, and I wanted to be able to mark angles with arcs to similarly show angle congruence. Relationships among objects are important as well--one should be able to construct a segment from two point objects easily, or a triangle from three points, and after construction, the individual points of a segment, or points and segments of a triangle should be accessible to the user. An auxillary goal of the project was to be able to do mathematics with the drawn objects once they are created, and to be able to get information or new related objects from each item implemented. Thus, line segments should know their length and slope, and should be able to return their midpoint. With just this functionality in place, a number of interesting diagrams could be created, and a solid foundation would be laid for creating more complex geometric constructions.
We created a class for each object which was to be represented--Point, Segment, Triangle--referring to a point with an optional label, a line segment with two points on the ends, and a collection of three points and the line segments connecting them. In the rest of the paper, we will use the capitalized class names to refer to their coded representations, and terms like 'point' and 'line' to refer to the general geometric objects. These classes inherit from Sage's Graphics object, which makes sense for a few reasons. First, this allows them to be treated like any shape built into Sage, and can thus be added to other Graphics instances to overlay them, or displayed using the show() function. Furthermore, because Graphics acts as a container for Sage's Graphics primitives, it was easy to build up and store our more complicated constructions from Sage's builtin plotting primitives. Some subtle bugs were introduced as a result of inheriting from Graphics, and it did require becoming intimately familiar with some aspects of Sage's drawing code, but the slick infterface it provides is certainly worth it.
Point was the first class which we implemented. Point takes a set of coordinates, an optional label, and some drawing style options, and draws the point and label accordingly. Drawing a round point mark in Sage is trivial, so it was the creation of the labels which provided the major hurdle here. In fact, drawing a piece of text in Sage is not hard either, but getting it to appear in the correct location with respect to the point (and not overlapping) was more difficult. While working on this, I found that Sage's text object did have an alignment argument to set where it is to appear compared to the coordinates provided for its location. However, use of that parameter alone did not always put labels into the correct location. Therefore, I added an extra argument to the constructor of Point which will nudge the label away from its corresponding point. Once the labels were looking nice, it was a simple matter of making sure that the points coordinates were available to the outside world (points don't have much other info to offer).
With Point in place, I started work on Segment. Segment accepts a pair of Points, or a pair of coordinates and pair of optional labels, as well as some style options as input, and constructs the segment connecting those points. Getting Segment to draw properly was fairly straightforward, and so I turned my attention to being able to place a congruence tick mark at the center of the segment. In working out the details, I realized that I would need several pieces of information available to me, which I had been planning on adding methods to compute anyway. Thus, I added methods to compute the slope, length, and midpoint of the segment. Segment congruence marks should be perpendicular to the segment, and should also be a set length, so I produced an algorithm which finds two points on the line which is perpendicular to the segment and passes through its midpoint. These points are are equidistant from the midpoint, and that distance is given by a constructor option. With coordinates in hand, a line segment primitive is added to Segment, assuming the option to draw a congruence mark is enabled.
{{{id=6| s1 = Segment([p1, p2], drawpoints=True) s2 = Segment([p2, p3], drawpoints=True) show(s1 + p3, aspect_ratio=1, axes=False, figsize=[5,5]) ///With the building blocks in place, my partner Bill went to work on a Triangle class. Like Segment, Triangle accepts either Points or a list of three coordinates and a list of optional labels. During initialization, Triangle creates its Points if necessary, and also the Segments which connect them, both of which are made available to the user through methods. We wished to have a convenient way to add congruence marks to the sides of our Triangle during construction, and so, Bill implemented a rather fancy interface which uses an internal class to accept a string of function calls from the user indicating which segment to draw the tick marks on. TriangleBuilder adds all of the necessary primitives to the underlying Graphics object, and the user gets a Triangle back, as expected.
{{{id=16| t1 = Triangle([(0,0), (0,1), (1,1)], drawpoints=True) show(t1, aspect_ratio=1, axes=False) ///The project, while having to shed some planned functionality for the sake of deadlines, did produce some very useful and useable code. Overall, the design and implementation are cleanly done, and should provide a solid foundation for further such work if I choose to pursue it. Given more time, a few more features would have nicely rounded out the functionality which was finished. Particularly, I would have liked to make the congruence mark drawing routine robust enough to support more than a single tick mark, and to have implemented the planned Angle class with the ability to draw angle arcs for congruence marks. Also, the current interface for choosing where to place labels is ugly, and the constructors for Segment and Triangle should take label placement data when Point objects are not passed in. In reviewing the code, I have realized that the algorithm for placing tick marks on segments is more complicated than it should be, and could use reimlementation. Further features would include routines for doing things like returning the Point where two Segments intersect, checking whether a Point is on a Segment, implementing a more general Line class, adding classes for circles, quadrilaterals, and more complex polygons, and exploration of working with these objects without explicit reference to x/y coordinates. For now, I am pleased with my results, and look forward to further hacking with the possibility of contributing some form of this code to Sage in the future.