Basic concepts

The basics

The underlying drawing model is that you make shapes, or add points to paths, and these are filled and/or stroked, using the current graphics state, which specifies colors, line thicknesses, and opacity. You can modify the current drawing environment by transforming/rotating/scaling it. This affects subsequent graphics but not the ones you've already drawn.

Specify points (positions) using Point(x, y). The default origin is at the top left of the drawing area, but you can reposition it at any time. Many of the drawing functions have an action argument. This can be :nothing, :fill, :stroke, :fillstroke, :fillpreserve, :strokepreserve, :clip. The default is :nothing.

The main defined types are Point, Drawing, and Tiler. Drawing is how you create new drawings. You can divide up the drawing area into areas, using Tiler, Partition, and Table, and define grids, using GridRect and GridHex. The BoundingBox type provides tools for working with rectangular extents.

Points and coordinates

The Point type holds two coordinates, x and y. For example:

julia> P = Point(12.0, 13.0)
Luxor.Point(12.0, 13.0)

julia> P.x
12.0

julia> P.y
13.0

Points are immutable, so you can't change P's x or y values directly. But it's easy to make new points based on existing ones.

Points can be added together:

julia> Q = Point(4, 5)
Luxor.Point(4.0, 5.0)

julia> P + Q
Luxor.Point(16.0, 18.0)

You can add or multiply Points and scalars:

julia> 10P
Luxor.Point(120.0, 130.0)

julia> P + 100
Luxor.Point(112.0, 113.0)

You can also make new points by mixing Points and tuples:

julia> P + (10, 0)
Luxor.Point(22.0, 13.0)

julia> Q * (0.5, 0.5)
Luxor.Point(2.0, 2.5)

You can use the letter O as a shortcut to refer to the current Origin, Point(0, 0).

rulers()
box.([O + (i, 0) for i in range(0, stop=200, length=5)], 20, 20, :stroke)
┌ Warning: The function `cfunction` is now written as a macro `@cfunction`.
│   caller = get_stream_callback at Cairo.jl:145 [inlined]
└ @ Core Cairo.jl:145

point example

Angles are usually supplied in radians, measured starting at the positive x-axis turning towards the positive y-axis (which usually points 'down' the page or canvas, so 'clockwise'). (Turtle graphics conventionally let you supply angles in degrees.)

Coordinates are interpreted as PostScript points, where a point is 1/72 of an inch.

Because Julia allows you to combine numbers and variables directly, you can supply units with dimensions and have them converted to points (assuming the current scale is 1:1):

For example:

rect(Point(20mm, 2cm), 5inch, (22/7)inch, :fill)

Drawings

Drawings and files

To create a drawing, and optionally specify the filename and type, and dimensions, use the Drawing constructor function.

Luxor.DrawingType.

Create a new drawing, and optionally specify file type (PNG, PDF, SVG, or EPS) and dimensions.

Drawing()

creates a drawing, defaulting to PNG format, default filename "luxor-drawing.png", default size 800 pixels square.

You can specify the dimensions, and assume the default output filename:

Drawing(400, 300)

creates a drawing 400 pixels wide by 300 pixels high, defaulting to PNG format, default filename "luxor-drawing.png".

Drawing(400, 300, "my-drawing.pdf")

creates a PDF drawing in the file "my-drawing.pdf", 400 by 300 pixels.

Drawing(1200, 800, "my-drawing.svg")`

creates an SVG drawing in the file "my-drawing.svg", 1200 by 800 pixels.

Drawing(width, height, surfacetype, [filename])

creates a new drawing of the given surface type (e.g. :svg, :png), storing the image only in memory if no filename is provided.

Drawing(1200, 1200/Base.Mathconstants.golden, "my-drawing.eps")

creates an EPS drawing in the file "my-drawing.eps", 1200 wide by 741.8 pixels (= 1200 ÷ ϕ) high. Only for PNG files must the dimensions be integers.

Drawing("A4", "my-drawing.pdf")

creates a drawing in ISO A4 size (595 wide by 842 high) in the file "my-drawing.pdf". Other sizes available are: "A0", "A1", "A2", "A3", "A4", "A5", "A6", "Letter", "Legal", "A", "B", "C", "D", "E". Append "landscape" to get the landscape version.

Drawing("A4landscape")

creates the drawing A4 landscape size.

PDF files default to a white background, but PNG defaults to transparent, unless you specify one using background().

source
Luxor.paper_sizesConstant.

The paper_sizes Dictionary holds a few paper sizes, width is first, so default is Portrait:

"A0"      => (2384, 3370),
"A1"      => (1684, 2384),
"A2"      => (1191, 1684),
"A3"      => (842, 1191),
"A4"      => (595, 842),
"A5"      => (420, 595),
"A6"      => (298, 420),
"A"       => (612, 792),
"Letter"  => (612, 792),
"Legal"   => (612, 1008),
"Ledger"  => (792, 1224),
"B"       => (612, 1008),
"C"       => (1584, 1224),
"D"       => (2448, 1584),
"E"       => (3168, 2448))
source

To finish a drawing and close the file, use finish(), and, to launch an external application to view it, use preview().

If you're using Jupyter (IJulia), preview() tries to display PNG and SVG files in the next notebook cell.

jupyter

If you're using Juno, then PNG and SVG files should appear in the Plots pane.

juno

Luxor.finishFunction.
finish()

Finish the drawing, and close the file. You may be able to open it in an external viewer application with preview().

source
Luxor.previewFunction.
preview()

If working in Jupyter (IJulia), display a PNG or SVG file in the notebook.

If working in Juno, display a PNG or SVG file in the Plot pane.

Otherwise:

  • on macOS, open the file in the default application, which is probably the Preview.app for PNG and PDF, and Safari for SVG
  • on Unix, open the file with xdg-open
  • on Windows, pass the filename to explorer.
source

The global variable currentdrawing (of type Drawing) stores some parameters related to the current drawing:

julia> fieldnames(typeof(currentdrawing))
10-element Array{Symbol,1}:
:width
:height
:filename
:surface
:cr
:surfacetype
:redvalue
:greenvalue
:bluevalue
:alpha

Quick drawings with macros

The @svg, @png, and @pdf macros are designed to let you quickly create graphics without having to provide the usual boiler-plate functions. For example, the Julia code:

@svg circle(O, 20, :stroke) 50 50

expands to

Drawing(50, 50, "luxor-drawing-(timestamp).png")
origin()
background("white")
sethue("black")
circle(O, 20, :stroke)
finish()
preview()

They just save a bit of typing. You can omit the width and height (defaulting to 600 by 600), and you don't have to specify a filename. For multiple lines, use either:

@svg begin
        setline(10)
        sethue("purple")
        circle(O, 20, :fill)
     end

or

@svg (setline(10);
      sethue("purple");
      circle(O, 20, :fill);
     )
Luxor.@svgMacro.
@svg drawing-instructions [width] [height] [filename]

Create and preview an SVG drawing, optionally specifying width and height (the default is 600 by 600). The file is saved in the current working directory as filename if supplied, or luxor-drawing-(timestamp).svg.

Examples

@svg circle(O, 20, :fill)

@svg circle(O, 20, :fill) 400

@svg circle(O, 20, :fill) 400 1200

@svg circle(O, 20, :fill) 400 1200 "images/test.svg"

@svg begin
        setline(10)
        sethue("purple")
        circle(O, 20, :fill)
     end


@svg begin
        setline(10)
        sethue("purple")
        circle(O, 20, :fill)
     end 1200, 1200
source
Luxor.@pngMacro.
@png drawing-instructions [width] [height] [filename]

Create and preview an PNG drawing, optionally specifying width and height (the default is 600 by 600). The file is saved in the current working directory as filename, if supplied, or luxor-drawing(timestamp).png.

Examples

@png circle(O, 20, :fill)

@png circle(O, 20, :fill) 400

@png circle(O, 20, :fill) 400 1200

@png circle(O, 20, :fill) 400 1200 "images/round.png"

@png begin
        setline(10)
        sethue("purple")
        circle(O, 20, :fill)
     end


@png begin
        setline(10)
        sethue("purple")
        circle(O, 20, :fill)
     end 1200 1200
source
Luxor.@pdfMacro.
@pdf drawing-instructions [width] [height] [filename]

Create and preview an PDF drawing, optionally specifying width and height (the default is 600 by 600). The file is saved in the current working directory as filename if supplied, or luxor-drawing(timestamp).pdf.

Examples

@pdf circle(O, 20, :fill)

@pdf circle(O, 20, :fill) 400

@pdf circle(O, 20, :fill) 400 1200

@pdf circle(O, 20, :fill) 400 1200 "images/A0-version.pdf"

@pdf begin
        setline(10)
        sethue("purple")
        circle(O, 20, :fill)
     end

@pdf begin
        setline(10)
        sethue("purple")
        circle(O, 20, :fill)
     end 1200, 1200
source

Drawings in memory

You can choose to store the drawing in memory. The advantage to this is that in-memory drawings are quicker, and can be passed as Julia data. This syntax for the Drawing() function:

Drawing(width, height, surfacetype, [filename])

lets you supply surfacetype as a symbol (:svg or :png). This creates a new drawing of the given surface type and stores the image only in memory if no filename is supplied. In a Jupyter notebook you can use Interact.jl to provide faster manipulations:

using Interact

function makecircle(r)
    d = Drawing(300, 300, :svg)
    sethue("black")
    origin()
    setline(0.5)
    hypotrochoid(150, 100, r, :stroke)
    finish()
    return d
end

@manipulate for r in 5:150
    makecircle(r)
end

The drawing surface

The origin (0/0) starts off at the top left: the x axis runs left to right across the page, and the y axis runs top to bottom down the page.

The origin() function moves the 0/0 point to the center of the drawing. It's often convenient to do this at the beginning of a program. You can use functions like scale(), rotate(), and translate() to change the coordinate system.

background() fills the drawing with a color, covering any previous contents. By default, PDF drawings have a white background, whereas PNG drawings have no background so that the background appears transparent in other applications. If there is a current clipping region, background() fills just that region. In the next example, the first background() fills the entire drawing with magenta, but the calls in the loop fill only the active clipping region, a table cell defined by the Table iterator:

background("magenta")
origin()
table = Table(5, 5, 100, 50)
for (pos, n) in table
    box(pos,
        table.colwidths[table.currentcol],
        table.rowheights[table.currentrow],
        :clip)
    background(randomhue()...)
    clipreset()
end
┌ Warning: The function `cfunction` is now written as a macro `@cfunction`.
│   caller = get_stream_callback at Cairo.jl:145 [inlined]
└ @ Core Cairo.jl:145

background

The rulers() function draws a couple of rulers to indicate the position and orientation of the current axes.

background("gray80")
origin()
rulers()
┌ Warning: The function `cfunction` is now written as a macro `@cfunction`.
│   caller = get_stream_callback at Cairo.jl:145 [inlined]
└ @ Core Cairo.jl:145

axes

Luxor.backgroundFunction.
background(color)

Fill the canvas with a single color. Returns the (red, green, blue, alpha) values.

Examples:

background("antiquewhite")
background("ivory")

If Colors.jl is installed:

background(RGB(0, 0, 0))
background(Luv(20, -20, 30))

If you don't specify a background color for a PNG drawing, the background will be transparent. You can set a partly or completely transparent background for PNG files by passing a color with an alpha value, such as this 'transparent black':

background(RGBA(0, 0, 0, 0))
source
Luxor.rulersFunction.

Draw and label two rulers starting at O, the current 0/0, and continuing out along the current positive x and y axes.

source
Luxor.originFunction.
origin()

Reset the current matrix, and then set the 0/0 origin to the center of the drawing (otherwise it will stay at the top left corner, the default).

You can refer to the 0/0 point as O. (O = Point(0, 0)),

source
origin(x, y)

Reset the current matrix, then move the 0/0 position relative to the top left corner of the drawing.

source
origin(pt:Point)

Reset the current matrix, then move the 0/0 position to pt.

source

Save and restore

gsave() saves a copy of the current graphics settings (current axis rotation, position, scale, line and text settings, color, and so on). When the next grestore() is called, all changes you've made to the graphics settings will be discarded, and the previous settings are restored, so things return to how they were when you last used gsave(). gsave() and grestore() should always be balanced in pairs.

You can also use the @layer macro to enclose graphics commands:

@svg begin
    circle(O, 100, :stroke)
    @layer (sethue("red"); rule(O); rule(O, pi/2))
    circle(O, 200, :stroke)
end

or

@svg begin
    circle(O, 100, :stroke)
    @layer begin
        sethue("red")
        rule(O)
        rule(O, pi/2)
    end
    circle(O, 200, :stroke)
end
Luxor.gsaveFunction.
gsave()

Save the current color settings on the stack.

source
Luxor.grestoreFunction.
grestore()

Replace the current graphics state with the one on top of the stack.

source