2D Graphics

From Norsemathology
Jump to navigation Jump to search

Working with Graphics In Sage

2D Graphics in Sage is done using a Graphics Object container that holds graphics primitives which can be draw using the show method.

Sage provides the following graphics primitives:

  • Arrow
  • Circle
  • Disk
  • Line
  • Point
  • Text
  • Polygon

Creating a graphics object is very simple:

g = Graphics()

Then to add items to a Graphics object you use the += operator.

g = Graphics()
g += line( [ (0,0), (2,2) ] )

The object is then displayed using the show method. For a more detailed description of the show method please read the section on 2D Plotting.

g = Graphics()
g += line( [ (0,0), (2,2) ] )

Let's start with a simple example that draws arrows pointing outward around a circle.

g = Graphics()
g += circle( (0,0), 1, rgbcolor=(1,0,0) )
g += sum([arrow((0,0), (cos(x),sin(x)) ) for x in [0..2*pi,step=0.1]])

Let's look at a bit closer at what this is doing.

First we create our Graphics object g

g = Graphics()

Next we add a circle to a our Graphics object. The parameters to circle tell it to be centered at the origin with a radius of one and a color of red.

g += circle( (0,0), 1, rgbcolor=(1,0,0) )

Now we add our arrows around the circle. Arrows are drawn from the origin to cos(x), sin(x) for the values 0 to 2PI with increments of 0.1. The first tuple passed to arrow specifies the start and the second parameter specifies the end where the arrow head will appear.

What may seem weird at first is that we are using the sum function with the arrows. This is possible because all Graphics objects support the + operator which just combines the objects together.

g += sum([arrow((0,0), (cos(x),sin(x)) ) for x in [0..2*pi,step=0.1]])

We then just call the show method to display our image:


The result is the following image:

Arrow circle.png

Generating a Koch Snowflake

Let's look how we can use Sage to generate a Koch Snowflake.

A Koch snowflake is generated by taking a line segment dividing it into thirds, removing the middle section and creating a equilateral triangle with it's base as the middle section.

Dr. Wilkinson has a Mathematica function that generates a Koch Snowflake by accepting Line parameters and using Mathematica's replace all function to subdivide the line. In Sage this isn't possible because once a Graphics primitive such as a line has been created it's properties can never be modified or accessed. This doesn't mean we can't create a Koch Snowflake it just means that we have to go about in a different than we would with Mathematica.

Let's first look at the function to subdivide a line segment.

def subdivideLine( segment ):
    x1 = segment[0][0] ; x2 = segment[1][0]
    y1 = segment[0][1] ; y2 = segment[1][1]
    p1 = ( (( 2 * x1 + x2 ) / 3) , (( 2*y1 + y2)/ 3 ) )
    p2 = ( ((1/6)*(3*x1 + sqrt(3)*y1 + 3*x2 -sqrt(3)*y2)), ((1/6) * (-sqrt(3)*x1 + 3*y1 + sqrt(3)*x2 + 3*y2 )) )
    p3 = ( (( x1 + 2*x2)/ 3) , ((y1 + 2*y2)/3) )
    result = [ [ segment[0], p1 ],[ p1, p2 ],[ p2, p3 ], [ p3, segment[1] ] ]
    return result

The function subdivideLine is pretty straightforward the one thing to note is the parameter segment. segment is a list of 2 tuples where each tuple contains an x and y location. For the rest of this section we will think of a line segment as being described by a list of two tuples.

Ex: [ (0,0), (1,0) ]

When subdivide line is complete it returns a list of lists of tuples, or in other words a list of line segments.

Now let's take a look at the kochsFractal function:

def kochsFractal( segments, iterations ):
    while iterations > 0:
        s = []
        for x in segments:
            s += subdivideLine( x )
        segments = s
        iterations -= 1 
    g = Graphics()
    for cur in segments:
        g += line( cur )
    return g

kochsFractal takes two parameters:

  • segments: a list of line segments
  • iterations: the number of times to subdivide the line segments

How it works is each time we go through the while loop we set a s to an empty list and go through each line segment in segments subdividing each line segment and adding the results to s. Afterward we set segments equal to s so the next time through we subdivide all of the new line segments we generated from the previous iteration. At the end we just take all of the line segments and add them to a new graphics object.

Here is a sample call to kochsFractal where we give a 1x1 square as our initial line segments and do 5 iterations.

kochsFractal( [ [ (0,0),(1,0) ], [ (1,0),(1,1) ], [ (1,1),(0,1) ], [ (0,1),(0,0) ] ], 5 ).show(axes=False,aspect_ratio=1)

The result looks like this:

Sage koch snowflake.png

Generating Sierpinski's Carpet

Here we will demonstrate how to use Sage to generate a Sierpinski Carpet.

A Sierpinski Carpet is generated by taking a square and dividing it into a grid of 3x3 squares and removing the middle square. The same pattern is then applied to each square in the grid recursively for the number of iterations specified.

Sage doesn't have a graphics primitive for square or rectangle so we will use the graphics primitive polygon for creating our square.

The basic algorithm works like this:

  • Draw the initial square in purple
  • Divide the square into a grid
  • Draw a white square in the middle of each square in the grid

Let's look at the function for finding the middle square:

def getDeletedSquare( loc ):
    a = loc[0] ; b = loc[1]
    dx = (b[0] - a[0])/3
    dy = (b[1] - a[1])/3
    return polygon([(a[0]+dx,a[1]+2*dy),(a[0]+dx,a[1]+dy),(a[0]+2*dx,a[1]+dy),(a[0]+2*dx,a[1]+2*dy)],rgbcolor=(1,1,1))

The function getDeletedSquare takes one parameter:

  • loc: A list of two tuples the first tuple specifies the bottom left corner of the square and the second tuple specifies the top right corner of the square.

getDeletedSquares then returns a polygon specified as a white square at the location of the middle square in the grid.

Now let's look at the function for generating the Sierpinski Carpet:

def sierpinskiCarpet( loc, iterations ):
    g = Graphics()
    a = loc[0] ; b = loc[1]
    dx = b[0] - a[0] ; dy = b[1] - a[1]
    g += polygon([(a[0],a[1]+dy),a,(b[0],b[1]-dy),b],rgbcolor=(0.5,0,0.5))
    while iterations > 0:
        squares=[getDeletedSquare([ (x,y), (x+dx,y+dy) ] )
                        for x in [a[0]..b[0],step=dx] for y in [a[1]..b[1],step=dy] ]
        for s in squares:
            g += s
        dx /= 3 ; dy /= 3
        iterations -= 1
    return g

sierpinskiCarpet takes two parameters:

  • loc: A list of two tuples where the first tuple is the bottom left corner of the square and the second tuple is the top right corner of the square.
  • iterations: The number of iterations to perform

The function first draws the square specified by the loc parameter in purple then it iterates through the specified number of iterations repeatedly subdividing the grid and adding the deleted squares to the graphics object g.

Here is a sample call to sierpinskiCarpet

sierpinskiCarpet( [(-2,-2),(2,2)], 4 ).show(axes=False,aspect_ratio=1)

The result looks like this:

Sage carpet4.png

Note: as you increase the number of iterations above 4 in Sage the quality of the image decreases quickly.

Here is the result with 5 iterations in Sage:

Sage carpet.png

Here is the result with 5 iterations in Mathematica:

Mathematica carpet.png