Drawing with JyScript

New graphics features

JyScript's graphics commands are essentially the same as those in PiScript. There are a few novel features, however.

Alpha: Colors in PostScript are opaque. Java, however, allows colors to have a specified transparency by adding a fourth component called the alpha value.

Here is a figure drawn without transparency.

beginpage()

center()
scale(50)

setlinewidth(2)
newpath()
box(-2, -2, 4, 4)
fill(1,0,0)
stroke()

newpath()
circle(1, 1, 1.5)
fill(0, 0, 1)
stroke()

endpage()

Now we will give the blue used to paint the circle an alpha value of 0.6, roughly meaning it is 60% opaque.

beginpage()

center()
scale(50)

setlinewidth(2)
newpath()
box(-2, -2, 4, 4)
fill(1,0,0)
stroke()

newpath()
circle(1, 1, 1.5)
fill(0, 0, 1, 0.6)
stroke()

endpage()

An alpha value of 1 is opaque, while 0 is transparent.

Another way to do this is using the setcomposite function.

beginpage()

center()
scale(50)

setlinewidth(2)
newpath()
box(-2, -2, 4, 4)
fill(1,0,0)
stroke()

gsave()
setlinewidth(20)
setcomposite(0.6)     # <-----
newpath()
circle(1, 1, 1.5)
fill(0, 0, 1)
stroke()
grestore()

endpage()

You will want to think about why the circle's boundary appears as it does.

tstroke: You may stroke with the linewidth interpreted in the user coordinate system. This is actually the default behavior in PostScript.

beginpage()

center()
scale(75)

newpath()
for i in range(-2, 3):
    moveto(-2, i)
    lineto(2, i)
    moveto(i, -2)
    lineto(i, 2)
stroke(0.6)

gsave()
newpath()
moveto(-2, 0)
lineto(2, 0)
moveto(0, -2)
lineto(0, 2)
setlinewidth(2)
stroke()
grestore()

newpath()
circle([0, 0], 1)
tstroke([0, 0, 1, 0.6])  # <-----
setlinewidth(2)
stroke([1, 0, 0])

endpage()

Writing image files:

writePNG([300, 300, draw], "tstroke.png", 1.5)  

The second argument is a factor by which the dimensions of the panel are scaled. In this way, we may create high-resolution images.

We may also write JPEG files with

writeJPEG([300, 300, draw], "tstroke.jpg", 1.5, 0.8)  

where the third argument represents a compression factor.

Antialiasing: Since Java is drawing on a computer screen, our figures necessarily have a low-resolution. Antialiasing is a technique that helps create the illusion of smoothness by coloring pixels near the circles with varying shades of gray.

Without antialiasing With antialiasing

Antialiasing is used by default. You may change this, however, with antialiasing(False) and antialiasing(True). Rectangles sometimes appear fuzzy when antialiasing is on. (The following figures are drawn with purestroke, which we'll meet in a moment, turned off to emphasize the effect of antialiasing.)

Without antialiasing With antialiasing

Purestroke: By default, Java adds 0.5 to pixel coordinates when stroking lines. This typically makes horizontal and vertical lines, drawn with a line width of 1, look better.

This usually isn't what we want for mathematical illustrations since we often carefully compute our coordinates. By default, JyScript disables this behavior. However, if you are drawing lots of rectangles, you may wish to restore the Java's default behavior with purestroke(False).

To illustrate the kind of thing you might see, here is the graph of a cubic function drawn with and without purestroke.

default: purestroke(True) purestroke(False)

Notice how, without purestroke, the lines look sharper but the graph looks jagged. We may turn purestroke on and off as we please to fine tune a figure.

beginpage()

center()
scale(75)

purestroke(False)
newpath()
for i in range(-2, 3):
    moveto(-2, i)
    lineto(2, i)
    moveto(i, -2)
    lineto(i, 2)
stroke(0.6)

newpath()
moveto(-2, 0)
lineto(2, 0)
moveto(0, -2)
lineto(0, 2)
stroke()

purestroke(True)
newpath()
graph(lambda x: x**3 - x, -2, 2)
setlinewidth(2)
stroke([0,0,1])

endpage()

You will not typically need to adjust the antialiasing and purestroke features, but they can help you fine tune figures.

Working with multiple panels

With a few changes, we may include several figures into a single frame.

Let's see how to create this:

First, we need to import JyScript rather than JyModule.

from JyScript import *

Also, we will have different drawing functions for each of the figures. Consequently, our drawing functions and commands must have an argument to specify which figure to draw on.

def left(p):
    p.beginpage()

    p.center()
    p.scale(50)
    p.newpath()
    p.box(-1, -1, 2, 2)
    p.fill(0, 0, 1)
    p.stroke()
    p.endpage()

def right(p):
    p.beginpage()

    p.center()
    p.scale(50)
    p.newpath()
    p.box(-1, -1, 2, 2)
    p.fill(1, 0, 0)
    p.stroke()
    p.endpage()

Finally, we open the frame using a list.

openframe([ [ 200, 200, left], [200, 200, right] ])

Here's the whole thing:

from JyScript import *

def left(p):
    p.beginpage()

    p.center()
    p.scale(50)
    p.newpath()
    p.box(-1, -1, 2, 2)
    p.fill(0, 0, 1)
    p.stroke()
    p.endpage()

def right(p):
    p.beginpage()

    p.center()
    p.scale(50)
    p.newpath()
    p.box(-1, -1, 2, 2)
    p.fill(1, 0, 0)
    p.stroke()
    p.endpage()

openframe([ [ 200, 200, left], [200, 200, right] ])

Anynumber of figures may be added in the list.

openframe([ [ 200, 200, left], [200, 200, middle], [200, 200, right] ])

Another common way to place multiple figures inside a frame is with one large figure in the center and smaller ones around the borders designated by north, south, east and west.

Here we use a dictionary to indicate the placement of the figures.

from JyScript import *

def function(p):
    ....

def slider(p):
    ....

openframe( {"center": [300, 300, function], 
            "south": [300, 40, slider]})

or

openframe( {"c": [300, 300, function], 
            "s": [300, 40, slider]})

Under the hood

Behind the scenes, there is class, called JyPanel, that holds individual figures. We may create JyPanels directly. For example, the last example could have been coded as

top = JyPanel(300, 300, function)
bottom = JyPanel(300, 40, slider)
openframe({'c': top, 's': bottom})

The argument in the drawing functions we define is simply the JyPanel on which we would like to draw.

At times, it may be useful to work directly with a JyPanel. For instance, if all we want to do is create a PNG file, we may do this:

from JyScript import *

def draw(p):
    p.beginpage()
    p.box(50, 50, 100, 100)
    p.fill(0, 0, 1)
    p.stroke()
    p.endpage()

panel = JyPanel(200, 200, draw)
panel.writePNG('pngbox.png', 2)

Users with some Java experience may also create more complicated layouts by working with the JyPanels.

Applets

Applets will be converted in Java byte code that may be distributed in a browser.

To write applets, we need to use the JyScript module. The applet will also be a class that subclasses JApplet. The name of the class must agree with the name of the file.

Therefore, a typical beginning is something like:

from JyScript import *
from javax.swing import *

class BoxApplet(JApplet):

Our methods will be instance methods.

    def draw(self, p):
        p.beginpage()
        p.center()
        p.scale(50)
        p.box(self.pos, -1, 2, 2)
        p.fill(0, 0, 1)
        p.stroke()
    
        p.endpage()

Then we need to create a method to initialize the applet.

    def __init__(self):
        layoutapplet(200, 200, self.draw, self)

Here is the whole file:

from JyScript import *
from javax.swing import *

class BoxApplet(JApplet):
    def draw(self, p):
        p.beginpage()
        p.center()
        p.scale(50)
        p.box(-1, -1, 2, 2)
        p.fill(0, 0, 1)
        p.stroke()
    
        p.endpage()

    def __init__(self):
        layoutapplet(200, 200, self.draw, self)

A convenient way to test our applet is to add

testapplet(BoxApplet())

and test with jython BoxApplet.py.

To create something we may post on the web, remove the testapplet line, run

jythonc -j box.jar -c BoxApplet.py

and ignore the dire warning. This creates a jar file box.jar that may be read by an HTML file with the following tag:

<applet 
  code=BoxApplet 
  width=200 
  height=240
  archive=box.jar>
</applet>

Another example