Luxor.@drawMacro
@draw drawing-instructions [width] [height]

Preview an PNG drawing, optionally specifying width and height (the default is 600 by 600). The drawing is stored in memory, not in a file on disk.

Examples

@draw circle(O, 20, :fill)

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

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


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


@draw begin
         setline(10)
         sethue("purple")
         circle(O, 20, :fill)
      end 1200 1200
Luxor.@drawsvgMacro
@drawsvg begin
    body
end w h

Create and preview an SVG drawing. Like @draw but using SVG format.

Unlike @draw (PNG), there is no background, by default.

Luxor.@epsMacro
@eps drawing-instructions [width] [height] [filename]

Create and preview an EPS 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).eps.

On some platforms, EPS files are converted automatically to PDF when previewed.

Examples

@eps circle(O, 20, :fill)

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

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

@eps circle(O, 20, :fill) 400 1200 "/tmp/A0-version"

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

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

@eps begin
        setline(10)
        sethue("purple")
        circle(O, 20, :fill)
     end 1200 1200
Luxor.@imagematrixMacro
@imagematrix drawing-instructions [width=256] [height=256]

Create a drawing and return a matrix of the image.

This macro returns a matrix of pixels that represent the drawing produced by the vector graphics instructions. It uses the image_as_matrix() function.

The default drawing is 256 by 256 points.

You don't need finish() (the macro calls it), and it's not previewed by preview().

m = @imagematrix begin
        sethue("red")
        box(O, 20, 20, :fill)
    end 60 60

julia>  m[1220:1224] |> show
    ARGB32[ARGB32(0.0N0f8,0.0N0f8,0.0N0f8,0.0N0f8),
           ARGB32(1.0N0f8,0.0N0f8,0.0N0f8,1.0N0f8),
           ARGB32(1.0N0f8,0.0N0f8,0.0N0f8,1.0N0f8),
           ARGB32(1.0N0f8,0.0N0f8,0.0N0f8,1.0N0f8),
           ARGB32(1.0N0f8,0.0N0f8,0.0N0f8,1.0N0f8)]

If, for some strange reason you want to draw the matrix as another Luxor drawing again, use code such as this:

m = @imagematrix begin
        sethue("red")
        box(O, 20, 20, :fill)
        sethue("blue")
        box(O, 10, 40, :fill)
    end 60 60

function convertmatrixtocolors(m)
    return convert.(Colors.RGBA, m)
end

function drawimagematrix(m)
    d = Drawing(500, 500, "/tmp/temp.png")
    origin()
    w, h = size(m)
    t = Tiler(500, 500, w, h)
    mi = convertmatrixtocolors(m)
    @show mi[30, 30]
    for (pos, n) in t
        c = mi[t.currentrow, t.currentcol]
        setcolor(c)
        box(pos, t.tilewidth -1, t.tileheight - 1, :fill)
    end
    finish()
    return d
end

drawimagematrix(m)

Transparency

The default value for the cells in an image matrix is transparent black. (Luxor's default color is opaque black.)

julia> @imagematrix begin
       end 2 2
2×2 reinterpret(ARGB32, ::Array{UInt32,2}):
 ARGB32(0.0,0.0,0.0,0.0)  ARGB32(0.0,0.0,0.0,0.0)
 ARGB32(0.0,0.0,0.0,0.0)  ARGB32(0.0,0.0,0.0,0.0)

Setting the background to a partially or completely transparent value may give unexpected results:

julia> @imagematrix begin
       background(1, 0.5, 0.0, 0.5) # semi-transparent orange
       end 2 2
2×2 reinterpret(ARGB32, ::Array{UInt32,2}):
 ARGB32(0.502,0.251,0.0,0.502)  ARGB32(0.502,0.251,0.0,0.502)
 ARGB32(0.502,0.251,0.0,0.502)  ARGB32(0.502,0.251,0.0,0.502)

here the semi-transparent orange color has been partially applied to the transparent background.

julia> @imagematrix begin
           sethue(1., 0.5, 0.0)
       paint()
       end 2 2
2×2 reinterpret(ARGB32, ::Array{UInt32,2}):
 ARGB32(1.0,0.502,0.0,1.0)  ARGB32(1.0,0.502,0.0,1.0)
 ARGB32(1.0,0.502,0.0,1.0)  ARGB32(1.0,0.502,0.0,1.0)

picks up the default alpha of 1.0.

Luxor.@imagematrix!Macro
@imagematrix! buffer drawing-instructions [width=256] [height=256]

Like @imagematrix, but use an existing UInt32 buffer.

w = 200
h  = 150
buffer = zeros(UInt32, w, h)
m = @imagematrix! buffer juliacircles(40) 200 150;
Images.RGB.(m)
Luxor.@layerMacro
The `layer` macro is a shortcut for `gsave()` ... `grestore()`.
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 "/tmp/A0-version"

@pdf circle(O, 20, :fill) 400 1200 "/tmp/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
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 "/tmp/round"

@png circle(O, 20, :fill) 400 1200 "/tmp/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
Luxor.@polarMacro
@polar (p)

Convert a tuple of two numbers to a Point of x, y Cartesian coordinates.

@polar (10, pi/4)
@polar [10, pi/4]

produces

Luxor.Point(7.0710678118654755, 7.071067811865475)
Luxor.@savesvgMacro
@savesvg begin
    body
end w h

Like @drawsvg but returns the raw SVG code of the drawing in a string. Uses svgstring.

Unlike @draw (PNG), there is no background, by default.

Luxor.@setcolor_strMacro

Set the current color to a string using a macro.

For example:

setcolor"red"
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 "/tmp/test"

@svg circle(O, 20, :fill) 400 1200 "/tmp/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
Base.convertMethod
convert(Point, bbox::BoundingBox)

Convert a BoundingBox to a four-point clockwise polygon.

convert(Vector{Point}, BoundingBox())
Base.inMethod
in(pt, bbox::BoundingBox)

Test whether pt is inside bbox.

Base.randMethod
rand(bbox::BoundingBox)

Return a random Point that lies inside bbox.

Luxor.CircleFunction
Circle(t::Turtle, radius=1.0)

Draw a filled circle centered at the current position with the given radius.

Luxor.ForwardFunction
Forward(t::Turtle, d=1)

Move the turtle forward by d units. The stored position is updated.

Luxor.HueShiftFunction
HueShift(t::Turtle, inc=1.0)

Shift the Hue of the turtle's pen forward by inc. Hue values range between 0 and 360. (Don't start with black, otherwise the saturation and brightness values will be black.)

Luxor.MessageMethod
Message(t::Turtle, txt)

Write some text at the current position.

Luxor.OrientationFunction
Orientation(t::Turtle, r=0.0)

Set the turtle's orientation to r degrees. See also Turn.

Luxor.PencolorMethod
Pencolor(t::Turtle, r, g, b)

Set the Red, Green, and Blue colors of the turtle.

Luxor.PendownMethod
Pendown(t::Turtle)

Put that pen down and start drawing.

Luxor.PenupMethod
Penup(t::Turtle)

Pick that pen up and stop drawing.

Luxor.PenwidthMethod
Penwidth(t::Turtle, w)

Set the width of the line drawn.

Luxor.PopMethod
Pop(t::Turtle)

Lift the turtle's position and orientation off a stack.

Luxor.PushMethod
Push(t::Turtle)

Save the turtle's position and orientation on a stack.

Luxor.RectangleFunction
Rectangle(t::Turtle, width=10.0, height=10.0)

Draw a filled rectangle centered at the current position with the given radius.

Luxor.RepositionMethod
Reposition(t::Turtle, pos::Point)
Reposition(t::Turtle, x, y)

Reposition: pick the turtle up and place it at another position.

Luxor.TowardsMethod
Towards(t::Turtle, pos::Point)

Rotate the turtle to face towards a given point.

Luxor.TurnFunction
Turn(t::Turtle, r=5.0)

Increase the turtle's rotation by r degrees. See also Orientation.

Luxor._argb32_to_rgbaMethod
_argb32_to_rgba(i)

Convert a 32bit ARGB Int to a four value array:

_argb32_to_rgba(0xFF800000)

4-element Array{Float64,1}:
 1.0
 0.5019607843137255
 0.0
 0.0
Luxor._empty_neighbourhoodMethod

emptyneighbourhood(sample, w, h, cellsize, d, points, grid)

Uses entries in grid to check whether the sample point is more than d units away from any other point in points.

The region we're analyzing lies between the origin and Point(w, h)`.

Luxor.add_mesh_patchFunction
add_mesh_patch(pattern::Mesh, plist::Array{Point}, colors=Array{Colors.Colorant, 1})

Add a new patch to the mesh pattern in pattern.

The first three or four sides of the supplied points polygon define the three or four sides of the mesh shape.

The colors array define the color of each corner point. Colors are reused if necessary. At least one color should be supplied.

Luxor.add_mesh_patchFunction
add_mesh_patch(pattern::Mesh, bezierpath::BezierPath,
	colors=Array{Colors.Colorant, 1})

Add a new patch to the mesh pattern in pattern.

The first three or four elements of the supplied bezierpath define the three or four sides of the mesh shape.

The colors array define the color of each corner point. Colors are reused if necessary. At least one color should be supplied.

Use setmesh() to select the mesh, which will be used to fill shapes.

Luxor.addstopMethod
addstop(b::Blend, offset, col)
addstop(b::Blend, offset, (r, g, b, a))
addstop(b::Blend, offset, string)

Add a color stop to a blend. The offset specifies the location along the blend's 'control vector', which varies between 0 (beginning of the blend) and 1 (end of the blend). For linear blends, the control vector is from the start point to the end point. For radial blends, the control vector is from any point on the start circle, to the corresponding point on the end circle.

Examples:

blendredblue = blend(Point(0, 0), 0, Point(0, 0), 1)
addstop(blendredblue, 0, setcolor(sethue("red")..., .2))
addstop(blendredblue, 1, setcolor(sethue("blue")..., .2))
addstop(blendredblue, 0.5, sethue(randomhue()...))
addstop(blendredblue, 0.5, setcolor(randomcolor()...))
Luxor.anglethreepointsMethod
anglethreepoints(p1::Point, p2::Point, p3::Point)

Find the angle formed by two lines defined by three points.

Luxor.animateMethod
animate(movie::Movie, scene::Scene; creategif=false, framerate=30)

Create the movie defined in movie by rendering the frames define in scene.

Luxor.animateMethod
animate(movie::Movie, scenelist::Array{Scene, 1};
    creategif=false,
    framerate=30,
    pathname="",
    tempdirectory="",
    usenewffmpeg=true)

Create the movie defined in movie by rendering the frames define in the array of scenes in scenelist.

If creategif is true, the function attempts to call the ffmpeg utility on the resulting frames to build a GIF animation. This will be stored in pathname (an existing file will be overwritten; use a ".gif" suffix), or in (movietitle).gif in a temporary directory. ffmpeg should be installed and available, of course, if this is to work.

In suitable environments, the resulting animation is displayed in the Plots window.

Example

animate(bang, [
    Scene(bang, backdrop, 0:200),
    Scene(bang, frame1, 0:200, easingfunction=easeinsine)],
    creategif=true,
    pathname="/tmp/animationtest.gif")

The usenewffmpeg option, true by default, uses single-pass palette generation and more complex filtering provided by recent versions of the ffmpeg utility, mainly to cope with transparent backgrounds. If set to false, the behavior is the same as in previous versions of Luxor.

Luxor.arcFunction
arc(xc, yc, radius, angle1, angle2, action=:none)

Add an arc to the current path from angle1 to angle2 going clockwise, centered at xc, yc.

Angles are defined relative to the x-axis, positive clockwise.

Luxor.arcFunction
arc(centerpoint::Point, radius, angle1, angle2, action=:none)

Add an arc to the current path from angle1 to angle2 going clockwise, centered at centerpoint.

Luxor.arc2rFunction
  arc2r(c1::Point, p2::Point, p3::Point, action=:none)

Add a circular arc centered at c1 that starts at p2 and ends at p3, going clockwise, to the current path.

c1-p2 really determines the radius. If p3 doesn't lie on the circular path, it will be used only as an indication of the arc's length, rather than its position.

Luxor.arc2sagittaFunction
arc2sagitta(p1::Point, p2::Point, s, action=:none)

Make a clockwise arc starting at p1 and ending at p2 that reaches a height of s, the sagitta, at the middle. Might append to current path...

Return tuple of the center point and the radius of the arc.

Luxor.arrowFunction
arrow(start::Point, C1::Point, C2::Point, finish::Point, action=:stroke;
    linewidth       = 1.0,
    arrowheadlength = 10,
    arrowheadangle  = pi/8,
    startarrow      = false,
    finisharrow     = true,
    decoration      = 0.5,
    decorate        = nothing
    arrowheadfunction = nothing)

Draw a Bezier curved arrow, from start to finish, with control points C1 and C2. Arrow heads can be added/hidden by changing startarrow and finisharrow options.

The decorate keyword argument accepts a function that can execute code at one or more locations on the arrow's shaft. The inherited graphic environment is centered at each point on the shaft given by scalar or vector decoration, and the x-axis is aligned with the direction of the curve at that point.

Example

This code draws an arrow head that's filled with orange and outlined in green.

function myarrowheadfunction(originalendpoint, newendpoint, shaftangle)
    @layer begin
        setline(5)
        translate(newendpoint)
        rotate(shaftangle)
        sethue("orange")
        ngon(O, 20, 3, 0, :fill)
        sethue("green")
        ngon(O, 20, 3, 0, :stroke)
    end
end

@drawsvg begin
    background("white")
    arrow(O, 220, 0, π,
        linewidth=10,
        arrowheadlength=30,
        arrowheadangle=π/7,
        clockwise=true,
        arrowheadfunction = myarrowheadfunction)
end
Luxor.arrowFunction
arrow(start::Point, finish::Point, height::Vector, action=:stroke;
    keyword arguments...)

Draw a Bézier arrow between start and finish, with control points defined to fit in an imaginary box defined by the two supplied height values (see bezierfrompoints()). If the height values are different signs, the arrow will change direction on its way.

Keyword arguments are the same as arrow(pt1, pt2, pt3, pt4).

Example

arrow(pts[1], pts[end], [15, 15],
    decoration = 0.5,
    decorate = () -> text(string(pts[1])))
Luxor.arrowMethod
arrow(centerpos::Point, radius, startangle, endangle;
    linewidth          = 1.0,
    arrowheadlength    = 10,
    arrowheadangle     = π/8,
    decoration         = 0.5,
    decorate           = nothing,
    arrowheadfunction  = nothing,
    clockwise          = true)

Draw a curved arrow, an arc centered at centerpos starting at startangle and ending at endangle with an arrowhead at the end. Angles are measured clockwise from the positive x-axis.

Arrows don't use the current linewidth setting (setline()); you can specify the linewidth.

The decorate keyword argument accepts a zero-argument function that can execute code at one or more locations on the arrow's shaft. The inherited graphic environment is centered at points on the shaft between 0 and 1 given by scalar or vector decoration, and the x-axis is aligned with the direction of the curve at that point.

A triangular arrowhead is drawn by default. But you can pass a function to the arrowheadfunction keyword argument that accepts three arguments: the shaft end, the arrow head end, and the shaft angle. Thsi allows you to draw any shape arrowhead.

Luxor.arrowMethod
arrow(startpoint::Point, endpoint::Point;
    linewidth         = 1.0,
    arrowheadlength   = 10,
    arrowheadangle    = pi/8,
    decoration        = 0.5 or range(),
    decorate          = nothing,
    arrowheadfunction = nothing)

Draw a line between two points and add an arrowhead at the end. The arrowhead length will be the length of the side of the arrow's head, and the arrowhead angle is the angle between the sloping side of the arrowhead and the arrow's shaft.

Arrows don't use the current linewidth setting (setline()), and defaults to 1, but you can specify another value. It doesn't need stroking/filling, the shaft is stroked and the head filled with the current color.

Decoration

The decorate keyword argument accepts a function with zero arguments that can execute code at one or more locations on the arrow's shaft. The inherited graphic environment is centered at each point on the shaft between 0 and 1 given by scalar or vector decoration, and the x-axis is aligned with the direction of the curve at that point.

Arrowheads

A triangular arrowhead is drawn by default. But you can pass a function to the arrowheadfunction keyword argument that accepts three arguments: the shaft end, the arrow head end, and the shaft angle. Thsi allows you to draw any shape arrowhead.

Example

function redbluearrow(shaftendpoint, endpoint, shaftangle)
    @layer begin
        sethue("red")
        sidept1 = shaftendpoint  + polar(10, shaftangle + π/2 )
        sidept2 = shaftendpoint  - polar(10, shaftangle + π/2)
        poly([sidept1, endpoint, sidept2], :fill)
        sethue("blue")
        poly([sidept1, endpoint, sidept2], :stroke, close=false)
    end
end

@drawsvg begin
    background("white")
    arrow(O, O + (120, 120),
        linewidth=4,
        arrowheadlength=40,
        arrowheadangle=π/7,
        arrowheadfunction = redbluearrow)

    arrow(O, 100, 3π/2, π,
        linewidth=4,
        arrowheadlength=20,
        clockwise=false,arrowheadfunction=redbluearrow)
end 800 250
Luxor.arrowheadFunction
arrowhead(target[, action=:fill];
    shaftangle=0,
    headlength=10,
    headangle=pi/8)

Draw an arrow head. The arrowhead length will be the length of the side of the arrow's head, and the arrowhead angle is the angle between the sloping side of the arrowhead and the arrow's shaft.

This doesn't use the current linewidth setting (setline()), and defaults to 1, but you can specify another value.

Luxor.backgroundMethod
background(color)

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

Examples:

background("antiquewhite")
background(1, 0.0, 1.0)
background(1, 0.0, 1.0, .5)

If Colors.jl is installed:

background(RGB(0, 1, 0))
background(RGBA(0, 1, 0))
background(RGBA(0, 1, 0, .5))
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))

or

background(0, 0, 0, 0)

Returns a tuple (r, g, b, a) of the color that was used to paint the background.

Luxor.barchartMethod
barchart(values;
        boundingbox = BoundingBox(O + (-250, -120), O + (250, 120)),
        bargap=10,
        margin = 5,
        border=false,
        labels=false,
        labelfunction = (values, i, lowpos, highpos, barwidth, scaledvalue) -> begin
                label(string(values[i]), :n, highpos, offset=10)
          end,
        barfunction =  (values, i, lowpos, highpos, barwidth, scaledvalue) -> begin
            @layer begin
                setline(barwidth)
                line(lowpos, highpos, :stroke)
            end
          end)

Draw a barchart where each bar is the height of a value in the values array. The bars will be scaled to fit in a bounding box.

Text labels are drawn if the keyword labels=true.

Extended help

The function returns a vector of points; each is the bottom center of a bar.

Draw a Fibonacci sequence as a barchart:

fib(n) = n > 2 ? fib(n - 1) + fib(n - 2) : 1
fibs = fib.(1:15)
@draw begin
    fontsize(12)
    barchart(fibs, labels=true)
end

To control the drawing of the text and bars, define functions that process the end points:

mybarfunction(values, i, lowpos, highpos, barwidth, scaledvalue)

mylabelfunction(values, i, lowpos, highpos, barwidth, scaledvalue)

and pass them like this:

barchart(vals, barfunction=mybarfunction)
barchart(vals, labelfunction=mylabelfunction)
function myprologfunction(values, basepoint, minbarrange, maxbarrange, barchartheight)
    @layer begin
        setline(0.2)
        for i in 0:10:maximum(values)
            rule(boxbottomcenter(basepoint) + (0, -(rescale(i, minbarrange, maxbarrange) * barchartheight)))
        end
    end
end
Luxor.betweenFunction
between(bb::BoundingBox, x)

Find a point between the two corners of a BoundingBox corresponding to x, where x is typically between 0 and 1.

Luxor.betweenMethod
between(p1::Point, p2::Point, x)
between((p1::Point, p2::Point), x)

Find the point between point p1 and point p2 for x, where x is typically between 0 and 1. between(p1, p2, 0.5) is equivalent to midpoint(p1, p2).

Luxor.bezierMethod
bezier(t, A::Point, A1::Point, B1::Point, B::Point)

Return the result of evaluating the Bezier cubic curve function, t going from 0 to 1, starting at A, finishing at B, control points A1 (controlling A), and B1 (controlling B).

Luxor.beziercurvatureMethod
beziercurvature(t, A::Point, A1::Point, B1::Point, B::Point)

Return the curvature of the Bezier curve at t ([0-1]), given start and end points A and B, and control points A1 and B1. The value (kappa) will typically be a value between -0.001 and 0.001 for points with coordinates in the 100-500 range.

κ(t) is the curvature of the curve at point t, which for a parametric planar curve is:

\[\begin{equation} \kappa = \frac{\mid \dot{x}\ddot{y}-\dot{y}\ddot{x}\mid} {(\dot{x}^2 + \dot{y}^2)^{\frac{3}{2}}} \end{equation}\]

The radius of curvature, or the radius of an osculating circle at a point, is 1/κ(t). Values of 1/κ will typically be in the range -1000 to 1000 for points with coordinates in the 100-500 range.

TODO Fix overshoot...

...The value of kappa can sometimes collapse near 0, returning NaN (and Inf for radius of curvature).

Luxor.bezierfrompointsMethod
bezierfrompoints(startpoint::Point, pointonline1::Point,
    pointonline2::Point, endpoint::Point)

Given four points, return the Bezier curve that passes through all four points, starting at startpoint and ending at endpoint. The two middle points of the returned BezierPathSegment are the two control points that make the curve pass through the two middle points supplied.

Luxor.bezierfrompointsMethod
bezierfrompoints(ptslist::Array{Point, 1})

Given four points, return the Bezier curve that passes through all four points.

Luxor.bezierpathtopolyMethod
bezierpathtopoly(bezierpath::BezierPath; steps=10)

Convert a Bezier path (an array of Bezier segments, where each segment is a tuple of four points: anchor1, control1, control2, anchor2) to a polygon.

To make a Bezier path, use makebezierpath() on a polygon.

The steps optional keyword determines how many line sections are used for each path.

Luxor.beziersegmentanglesMethod
beziersegmentangles(pt1, pt2;
    out = deg2rad(45),
    in  = deg2rad(135))
)

Return a BezierPathSegment joining pt1 and pt2 making the angles out at the start and in at the end.

It's similar to the tikZ (a) to [out=135, in=45] (b) drawing instruction (but in radians obviously).

out is the angle between a line from pt1 to the outgoing Bézier handle makes with the horizontal. in is the angle that a line joining pt2 from the preceding Bézier handle makes with the horizontal. So:

drawbezierpath(beziersegmentangles(O, O + (100, 0),
    out = deg2rad(45),
    in  = 2π - deg2rad(45)),
    :stroke)

draws a shape resembling a piece of string fixed at each end and hanging down in the middle.

Luxor.bezierstrokeFunction
bezierstroke(point1, point2, width=0.0)

Return a BezierPath, a stroked version of a straight line between two points.

It wil have 2 or 6 Bezier path segments that define a brush or pen shape. If width is 0, the brush shape starts and ends at a point. Otherwise the brush shape starts and ends with the thick end.

To draw it, use eg drawbezierpath(..., :fill).

Luxor.beziertopolyMethod
beziertopoly(bpseg::BezierPathSegment; steps=10)

Convert a Bezier segment to a polygon (an array of points).

Luxor.bezier′Method

bezier′(t, A::Point, A1::Point, B1::Point, B::Point)

Return the first derivative of the Bezier function.

Luxor.bezier′′Method
bezier′′(t, A::Point, A1::Point, B1::Point, B::Point)

Return the second derivative of Bezier function.

Luxor.blendMethod
blend(centerpos1, rad1, centerpos2, rad2, color1, color2)

Create a radial blend.

Example:

redblue = blend(
    pos, 0,                   # first circle center and radius
    pos, tiles.tilewidth/2,   # second circle center and radius
    "red",
    "blue"
    )
Luxor.blendMethod
blend(from::Point, startradius, to::Point, endradius)

Create an empty radial blend.

Radial blends are defined by two circles that define the start and stop locations. The first point is the center of the start circle, the first radius is the radius of the first circle.

A new blend is empty. To add colors, use addstop().

Luxor.blendMethod
blend(pt1::Point, pt2::Point, color1, color2)

Create a linear blend.

Example:

redblue = blend(pos, pos, "red", "blue")
Luxor.blendMethod
blend(from::Point, to::Point)

Create an empty linear blend.

A blend is a specification of how one color changes into another. Linear blends are defined by two points: parallel lines through these points define the start and stop locations of the blend. The blend is defined relative to the current axes origin. This means that you should be aware of the current axes when you define blends, and when you use them.

To add colors, use addstop().

Luxor.blendadjustFunction
blendadjust(ablend, center::Point, xscale, yscale, rot=0)

Modify an existing blend by scaling, translating, and rotating it so that it will fill a shape properly even if the position of the shape is nowhere near the original location of the blend's definition.

For example, if your blend definition was this (notice the 1)

blendgoldmagenta = blend(
        Point(0, 0), 0,                   # first circle center and radius
        Point(0, 0), 1,                   # second circle center and radius
        "gold",
        "magenta"
        )

you can use it in a shape that's 100 units across and centered at pos, by calling this:

blendadjust(blendgoldmagenta, Point(pos.x, pos.y), 100, 100)

then use setblend():

setblend(blendgoldmagenta)
Luxor.blendmatrixMethod
blendmatrix(b::Blend, m)

Set the matrix of a blend.

To apply a sequence of matrix transforms to a blend:

A = [1 0 0 1 0 0]
Aj = cairotojuliamatrix(A)
Sj = scalingmatrix(2, .2) * Aj
Tj = translationmatrix(10, 0) * Sj
A1 = juliatocairomatrix(Tj)
blendmatrix(b, As)
Luxor.boundingboxesintersectMethod
boundingboxesintersect(bbox1::BoundingBox, bbox2::BoundingBox)
boundingboxesintersect(acorner1::Point, acorner2::Point, bcorner1::Point, bcorner2::Point)

Return true if the two bounding boxes intersect.

Luxor.boxFunction
box(tiles::Tiler, n::T where T <: Integer, action::Symbol=:none;
    vertices=false)

Draw a box in tile n of tiles tiles.

Luxor.boxFunction
box(cornerpoint1, cornerpoint2, action=:none;
    vertices=false)

Create a box (rectangle) between two points and do an action.

Use vertices=true to return an array of the four corner points: bottom left, top left, top right, bottom right.

reversepath reverses the direction of the path (and returns points in the order: bottom left, bottom right, top right, top left).

Luxor.boxFunction
box(tile::BoxmapTile, action::Symbol=:none; vertices=false)

Use a Boxmaptile to make or draw a rectangular box. Use vertices=true to obtain the coordinates.

Create boxmaps using boxmap().

Luxor.boxFunction
box(points::Array, action=:none)

Create a box/rectangle using the first two points of an array of Points to defined opposite corners.

Use vertices=true to return an array of the four corner points: bottom left, top left, top right, bottom right.

Luxor.boxFunction
box(bbox::BoundingBox, :action;
        vertices=false)

Make a box using the bounds in bbox.

Use vertices=true to return an array of the four corner points: bottom left, top left, top right, bottom right.

Luxor.boxFunction
box(pt::Point, width, height, action=:none; vertices=false)

Create a box/rectangle centered at point pt with width and height. Use vertices=true to return an array of the four corner points rather than draw the box.

reversepath reverses the direction of the path.

Luxor.boxFunction
box(x, y, width, height, action=:none)

Create a box/rectangle centered at point x/y with width and height.

Luxor.boxFunction
box(t::Table, cellnumber::Int, action::Symbol=:none; vertices=false)

Draw box cellnumber in table t.

Luxor.boxFunction
box(pt, width, height, cornerradii::Array, action=:none)

Draw a box/rectangle centered at point pt with width and height and round each corner by the corresponding value in the array cornerradii.

The constructed path consists of arcs and straight lines.

The first corner is the one at the bottom left, the second at the top left, and so on.

Example

@draw begin
    box(O, 120, 120, [0, 20, 40, 60], :fill)
end
Luxor.boxFunction
box(pt, width, height, cornerradius, action=:none)

Draw a box/rectangle centered at point pt with width and height and round each corner by cornerradius.

The constructed path consists of arcs and straight lines.

Luxor.boxMethod
box(t::Table, r::T, c::T, action::Symbol=:none) where T <: Integer

Draw a box in table t at row r and column c.

Luxor.boxaspectratioFunction
boxaspectratio(bb::BoundingBox=BoundingBox())

Return the aspect ratio (the height divided by the width) of bounding box bb.

Luxor.boxbottomcenterFunction
boxbottomcenter(bb::BoundingBox=BoundingBox())

Return the point at the bottom center of the BoundingBox bb, defaulting to the drawing extent.

⋅ ⋅ ⋅
⋅ ⋅ ⋅
⋅ ■ ⋅
Luxor.boxbottomleftFunction
boxbottomleft(bb::BoundingBox=BoundingBox())

Return the point at the bottom left of the BoundingBox bb, defaulting to the drawing extent.

⋅ ⋅ ⋅
⋅ ⋅ ⋅
■ ⋅ ⋅
Luxor.boxbottomrightFunction
boxbottomright(bb::BoundingBox=BoundingBox())

Return the point at the bottom right of the BoundingBox bb, defaulting to the drawing extent.

⋅ ⋅ ⋅
⋅ ⋅ ⋅
⋅ ⋅ ■
Luxor.boxdiagonalFunction
boxdiagonal(bb::BoundingBox=BoundingBox())

Return the length of the diagonal of bounding box bb.

Luxor.boxheightFunction
boxheight(bb::BoundingBox=BoundingBox())

Return the height of bounding box bb.

Luxor.boxmapMethod
boxmap(A::Array, pt, w, h)

Build a box map of the values in A with one corner at pt and width w and height h. There are length(A) boxes. The areas of the boxes are proportional to the original values, scaled as necessary.

The return value is an array of BoxmapTiles. For example:

[BoxmapTile(0.0, 0.0, 10.0, 20.0)
 BoxmapTile(10.0, 0.0, 10.0, 13.3333)
 BoxmapTile(10.0, 13.3333, 10.0, 6.66667)]

with each tile containing (x, y, w, h). box() and BoundingBox() can work with BoxmapTiles as well.

Example

using Luxor
@svg begin
    fontsize(16)
    fontface("HelveticaBold")
    pt = Point(-200, -200)
    a = rand(10:200, 15)
    tiles = boxmap(a, Point(-200, -200), 400, 400)
    for (n, t) in enumerate(tiles)
        randomhue()
        bb = BoundingBox(t)
        box(bb - 2, :stroke)
        box(bb - 5, :fill)
        sethue("white")
        text(string(n), midpoint(bb[1], bb[2]), halign=:center)
    end
end 400 400 "boxmap.svg"
Luxor.boxmiddlecenterFunction
boxmiddlecenter(bb::BoundingBox=BoundingBox())

Return the point at the center of the BoundingBox bb, defaulting to the drawing extent.

⋅ ⋅ ⋅
⋅ ■ ⋅
⋅ ⋅ ⋅
Luxor.boxmiddleleftFunction
boxmiddleleft(bb::BoundingBox=BoundingBox())

Return the point at the middle left of the BoundingBox bb, defaulting to the drawing extent.

⋅ ⋅ ⋅
■ ⋅ ⋅
⋅ ⋅ ⋅
Luxor.boxmiddlerightFunction
boxmiddleright(bb::BoundingBox=BoundingBox())

Return the point at the midde right of the BoundingBox bb, defaulting to the drawing extent.

⋅ ⋅ ⋅
⋅ ⋅ ■
⋅ ⋅ ⋅
Luxor.boxtopcenterFunction
boxtopcenter(bb::BoundingBox=BoundingBox())

Return the point at the top center of the BoundingBox bb, defaulting to the drawing extent.

⋅ ■ ⋅
⋅ ⋅ ⋅
⋅ ⋅ ⋅
Luxor.boxtopleftFunction
boxtopleft(bb::BoundingBox=BoundingBox())

Return the point at the top left of the BoundingBox bb, defaulting to the drawing extent.

■ ⋅ ⋅
⋅ ⋅ ⋅
⋅ ⋅ ⋅
Luxor.boxtoprightFunction
boxtopright(bb::BoundingBox=BoundingBox())

Return the point at the top right of the BoundingBox bb, defaulting to the drawing extent.

⋅ ⋅ ■
⋅ ⋅ ⋅
⋅ ⋅ ⋅
Luxor.boxwidthFunction
boxwidth(bb::BoundingBox=BoundingBox())

Return the width of bounding box bb.

Luxor.brushFunction
brush(pt1, pt2, width=10;
    strokes=10,
    minwidth=0.01,
    maxwidth=0.03,
    twist = -1,
    lowhandle  = 0.3,
    highhandle = 0.7,
    randomopacity = true,
    tidystart = false,
    action = :fill,
    strokefunction = (nbpb) -> nbpb))

Draw a composite brush stroke made up of some randomized individual filled Bezier paths.

strokefunction allows a function to process a BezierPathSegment or do other things before it's drawn.

Note

There is a lot of randomness in this function. Results are unpredictable.

Luxor.buildrowMethod
buildrow(A, x, y, w, h)

Make a row of tiles from A.

Luxor.cairotojuliamatrixMethod
cairotojuliamatrix(c)

Return a 3x3 Julia matrix that's the equivalent of the six-element matrix in c.

Luxor.carcFunction
carc(xc, yc, radius, angle1, angle2, action=:none)

Add an arc to the current path from angle1 to angle2 going counterclockwise, centered at xc/yc.

Angles are defined relative to the x-axis, positive clockwise.

Luxor.carcFunction
carc(centerpoint::Point, radius, angle1, angle2, action=:none)

Add an arc centered at centerpoint to the current path from angle1 to angle2, going counterclockwise.

Luxor.carc2rFunction
carc2r(c1::Point, p2::Point, p3::Point, action=:none)

Add a circular arc centered at c1 that starts at p2 and ends at p3, going counterclockwise, to the current path.

c1-p2 really determines the radius. If p3 doesn't lie on the circular path, it will be used only as an indication of the arc's length, rather than its position.

Luxor.carc2sagittaFunction
carc2sagitta(p1::Point, p2::Point, s, action=:none)

Make a counterclockwise arc starting at p1 and ending at p2 that reaches a height of s, the sagitta, at the middle. Might append to current path...

Return tuple of center point and radius of arc.

Luxor.center3ptsMethod
center3pts(a::Point, b::Point, c::Point)

Find the radius and center point for three points lying on a circle.

returns (centerpoint, radius) of a circle.

If there's no such circle, the function returns (Point(0, 0), 0).

If two of the points are the same, use circle(pt1, pt2) instead.

Luxor.circleFunction
circle(pt1::Point, pt2::Point, pt3::Point, action=:none)

Make a circle that passes through three points.

Luxor.circleFunction
circle(pt1::Point, pt2::Point, action=:none)

Make a circle that passes through two points that define the diameter:

Luxor.circleFunction
circle(x, y, r, action=:none)

Make a circle of radius r centered at x/y.

action is one of the actions applied by do_action, defaulting to :none. You can also use ellipse() to draw circles and place them by their centerpoint.

Luxor.circleFunction
circle(pt, r, action=:none)

Make a circle centered at pt.

Luxor.circlecircleinnertangentsMethod

circlecircleinnertangents(circle1center::Point, circle1radius, circle2center::Point, circle2radius)

Find the inner tangents of two circles. These are tangent lines that cross as they skim past one circle and touch the other.

Returns the four points: tangentpoint1 on circle 1, tangentpoint1 on circle2, tangentpoint2 on circle 1, tangentpoint2 on circle2.

Returns (O, O, O, O) if inner tangents can't be found (eg when the circles overlap).

Use circlecircleoutertangents() to find the outer tangents.

Luxor.circlecircleoutertangentsMethod
circlecircleoutertangents(cpt1::Point, r1, cpt2::Point, r2)

Return four points, p1, p2,p3,p4, where a line throughp1andp2, and a line throughp3andp4, form the outer tangents to the circles defined bycpt1/r1andcpt2/r2`.

Returns four identical points (O) if one of the circles lies inside the other.

Luxor.circlepathFunction
circlepath(center::Point, radius, action=:none;
    reversepath=false,
    kappa = 0.5522847498307936)

Draw a circle using Bézier curves.

The magic value, kappa, is 4.0 * (sqrt(2.0) - 1.0) / 3.0.

Luxor.circlepointtangentMethod
circlepointtangent(through::Point, radius, targetcenter::Point, targetradius)

Find the centers of up to two circles of radius radius that pass through point through and are tangential to a circle that has radius targetradius and center targetcenter.

This function returns a tuple:

  • (0, O, O) - no circles exist

  • (1, pt1, O) - 1 circle exists, centered at pt1

  • (2, pt1, pt2) - 2 circles exist, with centers at pt1 and pt2

(The O are just dummy points so that three values are always returned.)

Luxor.circletangent2circlesMethod
circletangent2circles(radius, circle1center::Point, circle1radius, circle2center::Point, circle2radius)

Find the centers of up to two circles of radius radius that are tangent to the two circles defined by circle1... and circle2.... These two circles can overlap, but one can't be inside the other.

  • (0, O, O) - no such circles exist

  • (1, pt1, O) - 1 circle exists, centered at pt1

  • (2, pt1, pt2) - 2 circles exist, with centers at pt1 and pt2

(The O are just dummy points so that three values are always returned.)

Luxor.clipMethod
clip()

Establish a new clipping region by intersecting the current clipping region with the current path and then clearing the current path.

An existing clipping region is enforced through and after a gsave()-grestore() block, but a clipping region set inside a gsave()-grestore() block is lost after grestore(). [?]

Luxor.clippreserveMethod
clippreserve()

Establish a new clipping region by intersecting the current clipping region with the current path, but keep the current path.

Luxor.clipresetMethod
clipreset()

Reset the clipping region to the current drawing's extent.

Luxor.closepathMethod
closepath()

Close the current path. This is Cairo's close_path() function.

Luxor.crescentFunction
crescent(pos, innerradius, outeradius, action=nothing;
    vertices=false,
    reversepath=false,
    steps = 30)

Create a crescent-shaped polygon, aligned with the current x-axis. If the inner radius is 0, you'll get a semicircle.

See also crescent(pos1, innerradius, pos2, outeradius...).

Examples

Create a filled crescent shape with outer radius of 200, inner radius of 130.

crescent(O, 130, 200, :fill)

Create a stroked crescent shape; the inner radius of 0 produces a semicircle.

crescent(O, 0, 200, :stroke)
Luxor.crescentFunction

crescent(cp1, r1, cp2, r2, action=nothing; vertices=false, reversepath=false)

Create a crescent-shaped polygon, aligned with the current x-axis, by finding the intersection of two circles. The two center positions should be different.

See also crescent(point, innerradius, outeradius...).

Examples

Create a filled crescent shape from two circles.

crescent(O, 100, O + (60, 0), 150, :fill)
Luxor.cropmarksMethod
cropmarks(center, width, height)

Draw cropmarks (also known as trim marks). Use current color.

Luxor.crossproductMethod
crossproduct(p1::Point, p2::Point)

This is the perp dot product, really, not the crossproduct proper (which is 3D):

Luxor.currentdrawingMethod
currentdrawing()

Return the current Luxor drawing, if there currently is one.

Luxor.curveMethod
curve(x1, y1, x2, y2, x3, y3)
curve(p1, p2, p3)

Add a Bézier curve.

The spline starts at the current position, finishing at x3/y3 (p3), following two control points x1/y1 (p1) and x2/y2 (p2).

Luxor.dimensionMethod
dimension(p1::Point, p2::Point;
    format::Function   = (d) -> string(d), # process the measured value into a string
    offset             = 0.0,              # left/right, parallel with x axis
    fromextension      = (10.0, 10.0),     # length of extensions lines left and right
    toextension        = (10.0, 10.0),     #
    textverticaloffset = 0.0,              # range 1.0 (top) to -1.0 (bottom)
    texthorizontaloffset = 0.0,            # range 1.0 (top) to -1.0 (bottom)
    textgap            = 5,                # gap between start of each arrow (≈ fontsize?)
    textrotation       = 0.0,
    arrowlinewidth     = 1.0,
    arrowheadlength    = 10,
    arrowheadangle     = π/8)

Calculate and draw dimensioning graphics for the distance between p1 and p2. The value can be formatted with function format.

p1 is the lower on the page (ie probably the higher y value) point, p2 is the higher on the page (ie probably lower y) point.

offset is to the left (-x) when negative.

Dimension graphics will be rotated to align with a line between p1 and p2.

In textverticaloffset, "vertical" and "horizontal" are best understood by "looking" along the line from the first point to the second. textverticaloffset ranges from -1 to 1, texthorizontaloffset in default units.

        toextension
        [5  ,  5]
       <---> <--->
                             to
       -----------            +
            ^
            |

           -50

            |
            v
       ----------            +
                            from
       <---> <--->
         [5 , 5]
       fromextension

            <---------------->
                  offset

Returns the measured distance and the text.

Luxor.distanceMethod
distance(p1::Point, p2::Point)

Find the distance between two points (two argument form).

Luxor.do_actionMethod
do_action(action)

This is usually called by other graphics functions. Actions for graphics commands include :fill, :stroke, :clip, :fillstroke, :fillpreserve, :strokepreserve, :none, and :path.

Luxor.dotproductMethod
dotproduct(a::Point, b::Point)

Return the scalar dot product of the two points.

Luxor.douglas_peuckerMethod

Use a non-recursive Douglas-Peucker algorithm to simplify a polygon. Used by simplify().

douglas_peucker(pointlist::Array, start_index, last_index, epsilon)
Luxor.drawbezierpathFunction
drawbezierpath(bezierpath::BezierPath, action=:none;
    close=true)

Draw the Bézier path, and apply the action, such as :none, :stroke, :fill, etc. By default the path is closed.

Luxor.drawbezierpathFunction
drawbezierpath(bps::BezierPathSegment, action=:none;
    close=false)

Draw the Bézier path segment, and apply the action, such as :none, :stroke, :fill, etc. By default the path is open.

Luxor.easeincircMethod
easeincirc(t, b, c, d)

circular easing in - accelerating from zero velocity

Luxor.easeincubicMethod
easeincubic(t, b, c, d)

cubic easing in - accelerating from zero velocity

Luxor.easeinexpoMethod
easeinexpo(t, b, c, d)

exponential easing in - accelerating from zero velocity

Luxor.easeinoutbezierFunction
easeinoutbezier(t, b, c, d, cpt1, cpt2)

This easing function takes six arguments, the usual t, b, c, and d, but also two points. These are the normalized control points of a Bezier curve drawn between Point(0, 0) to Point(1.0, 1.0). The y value of the Bezier is the eased value for t.

In your frame() generating function, if a Scene specifies the easeinoutbezier easing function, you can use this:

...
lineareasing = rescale(framenumber, 1, scene.framerange.stop)
beziereasing = scene.easingfunction(lineareasing, 0, 1, 1,
    Point(0.25, 0.25), Point(0.75, 0.75))
...

These two control points lie on the line between 0/0 and 1/1, so it's equivalent to a linear easing (lineartween() or easingflat).

However, in the next example, the two control points define a wave-like curve that changes direction before changing back. When animating with this easing function, an object will 'go retrograde' for a while.

lineareasing = rescale(framenumber, 1, scene.framerange.stop)
beziereasing = scene.easingfunction(lineareasing, 0, 1, 1,
    Point(0.01, 1.99), Point(0.99, -1.5))
Luxor.easeinoutcircMethod
easeinoutcirc(t, b, c, d)

circular easing in/out - acceleration until halfway, then deceleration

Luxor.easeinoutcubicMethod
easeinoutcubic(t, b, c, d)

cubic easing in/out - acceleration until halfway, then deceleration

Luxor.easeinoutexpoMethod
easeinoutexpo(t, b, c, d)

exponential easing in/out - accelerating until halfway, then decelerating

Luxor.easeinoutquadMethod
easeinoutquad(t, b, c, d)

quadratic easing in/out - acceleration until halfway, then deceleration

Luxor.easeinoutquartMethod
easeinoutquart(t, b, c, d)

quartic easing in/out - acceleration until halfway, then deceleration

Luxor.easeinoutquintMethod
easeinoutquint(t, b, c, d)

quintic easing in/out - acceleration until halfway, then deceleration

Luxor.easeinoutsineMethod
easeinoutsine(t, b, c, d)

sinusoidal easing in/out - accelerating until halfway, then decelerating

Luxor.easeinquadMethod
easeinquad(t, b, c, d)

quadratic easing in - accelerating from zero velocity

Luxor.easeinquartMethod
easeinquart(t, b, c, d)

quartic easing in - accelerating from zero velocity

Luxor.easeinquintMethod
easeinquint(t, b, c, d)

quintic easing in - accelerating from zero velocity

Luxor.easeinsineMethod
easeinsine(t, b, c, d)

sinusoidal easing in - accelerating from zero velocity

Luxor.easeoutcircMethod
easeoutcirc(t, b, c, d)

circular easing out - decelerating to zero velocity

Luxor.easeoutcubicMethod
easeoutcubic(t, b, c, d)

cubic easing out - decelerating to zero velocity

Luxor.easeoutexpoMethod
easeoutexpo(t, b, c, d)

exponential easing out - decelerating to zero velocity

Luxor.easeoutquadMethod
easeoutquad(t, b, c, d)

quadratic easing out - decelerating to zero velocity

Luxor.easeoutquartMethod
easeoutquart(t, b, c, d)

quartic easing out - decelerating to zero velocity

Luxor.easeoutquintMethod
easeoutquint(t, b, c, d)

quintic easing out - decelerating to zero velocity

Luxor.easeoutsineMethod
easeoutsine(t, b, c, d)

sinusoidal easing out - decelerating to zero velocity

Luxor.easingflatMethod
easingflat(t, b, c, d)

A flat easing function, same as lineartween().

For all easing functions, the four parameters are:

  • t time, ie the current framenumber
  • b beginning position or bottom value of the range
  • c total change in position or top value of the range
  • d duration, ie a framecount
  1. t/d or t/=d normalizes t to between 0 and 1
  2. ... * c scales up to the required range value
  3. ... + b adds the initial offset
Luxor.ellipseFunction
ellipse(focus1::Point, focus2::Point, k, action=:none;
        stepvalue=pi/100,
        vertices=false,
        reversepath=false)

Build a polygon approximation to an ellipse, given two points and a distance, k, which is the sum of the distances to the focii of any points on the ellipse (or the shortest length of string required to go from one focus to the perimeter and on to the other focus).

Luxor.ellipseFunction
ellipse(xc, yc, w, h, action=:none)

Make an ellipse, centered at xc/yc, fitting in a box of width w and height h.

Luxor.ellipseFunction
ellipse(focus1::Point, focus2::Point, pt::Point, action=:none;
        stepvalue=pi/100,
        vertices=false,
        reversepath=false)

Build a polygon approximation to an ellipse, given two points and a point somewhere on the ellipse.

Luxor.ellipseFunction
ellipse(cpt, w, h, action=:none)

Make an ellipse, centered at point c, with width w, and height h.

Luxor.ellipseinquadFunction
ellipseinquad(qgon, action=:none)

Calculate a Bézier-based ellipse that fits inside the quadrilateral qgon, an array of with at least four Points, then apply action.

Returns ellipsecenter, ellipsesemimajor, ellipsesemiminor, ellipseangle:

ellipsecenter the ellipse center

ellipsesemimajor ellipse semimajor axis

ellipsesemiminor ellipse semiminor axis

ellipseangle ellipse rotation

The function returns O, 0, 0, 0 if a suitable ellipse can't be found. (The qgon is probably not a convex polygon.)

References

http://faculty.mae.carleton.ca/John_Hayes/Papers/InscribingEllipse.pdf

Luxor.epitrochoidFunction
epitrochoid(R, r, d, action=:none;
        stepby=0.01,
        period=0,
        vertices=false)

Make a epitrochoid with short line segments. (Like a Spirograph.) The curve is traced by a point attached to a circle of radius r rolling around the outside of a fixed circle of radius R, where the point is a distance d from the center of the circle. Things get interesting if you supply non-integral values.

stepby, the angular step value, controls the amount of detail, ie the smoothness of the polygon.

If period is not supplied, or 0, the lowest period is calculated for you.

The function can return a polygon (a list of points), or draw the points directly using the supplied action. If the points are drawn, the function returns a tuple showing how many points were drawn and what the period was (as a multiple of pi).

Luxor.fillpathMethod
fillpath()

Fill the current path according to the current settings. The current path is then cleared.

Luxor.fillpreserveMethod
fillpreserve()

Fill the current path with current settings, but then keep the path current.

Luxor.findbeziercontrolpointsMethod
findbeziercontrolpoints(previouspt::Point,
    pt1::Point,
    pt2::Point,
    nextpt::Point;
    smooth_value=0.5)

Find the Bézier control points for the line between pt1 and pt2, where the point before pt1 is previouspt and the next point after pt2 is nextpt.

Luxor.finishMethod
finish()

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

Luxor.fontfaceMethod
fontface(fontname)

Select a font to use. (Toy API)

Luxor.fontsizeMethod
fontsize(n)

Set the font size to n units. The default size is 10 units. (Toy API)

Luxor.get_fontsizeMethod
get_fontsize()

Return the font size set by fontsize or. more precisely. the y-scale of the Cairo font matrix if Cairo.set_font_matrix is used directly. (Toy API)

This only works if Cairo is at least at v1.0.5.

Luxor.getmatrixMethod
getmatrix()

Get the current matrix. Returns an array of six float64 numbers:

  • xx component of the affine transformation

  • yx component of the affine transformation

  • xy component of the affine transformation

  • yy component of the affine transformation

  • x0 translation component of the affine transformation

  • y0 translation component of the affine transformation

Some basic matrix transforms:

  • translate

transform([1, 0, 0, 1, dx, dy]) shifts by dx, dy

  • scale

transform([fx 0 0 fy 0 0]) scales by fx and fy

  • rotate

transform([cos(a), -sin(a), sin(a), cos(a), 0, 0]) rotates around to a radians

rotate around O: [c -s s c 0 0]

  • shear

transform([1 0 a 1 0 0]) shears in x direction by a

shear in y direction by a: [1 a 0 1 0 0]

  • x-skew

transform([1, 0, tan(a), 1, 0, 0]) skews in x by a

  • y-skew

transform([1, tan(a), 0, 1, 0, 0]) skews in y by a

  • flip

transform([fx, 0, 0, fy, centerx * (1 - fx), centery * (fy-1)]) flips with center at centerx/centery

  • reflect

transform([1 0 0 -1 0 0]) reflects in xaxis

transform([-1 0 0 1 0 0]) reflects in yaxis

When a drawing is first created, the matrix looks like this:

getmatrix() = [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]

When the origin is moved to 400/400, it looks like this:

getmatrix() = [1.0, 0.0, 0.0, 1.0, 400.0, 400.0]

To reset the matrix to the original:

setmatrix([1.0, 0.0, 0.0, 1.0, 0.0, 0.0])
Luxor.getmodeMethod
getmode()

Return the current compositing/blending mode as a string.

Luxor.getnearestpointonlineMethod
getnearestpointonline(pt1::Point, pt2::Point, startpt::Point)

Given a line from pt1 to pt2, and startpt is the start of a perpendicular heading to meet the line, at what point does it hit the line?

Luxor.getpathMethod
getpath()

Get the current path and return a CairoPath object, which is an array of element_type and points objects. With the results you can step through and examine each entry:

o = getpath()
x, y = currentpoint()
for e in o
      if e.element_type == Cairo.CAIRO_PATH_MOVE_TO
          (x, y) = e.points
          move(x, y)
      elseif e.element_type == Cairo.CAIRO_PATH_LINE_TO
          (x, y) = e.points
          # straight lines
          line(x, y)
          strokepath()
          circle(x, y, 1, :stroke)
      elseif e.element_type == Cairo.CAIRO_PATH_CURVE_TO
          (x1, y1, x2, y2, x3, y3) = e.points
          # Bezier control lines
          circle(x1, y1, 1, :stroke)
          circle(x2, y2, 1, :stroke)
          circle(x3, y3, 1, :stroke)
          move(x, y)
          curve(x1, y1, x2, y2, x3, y3)
          strokepath()
          (x, y) = (x3, y3) # update current point
      elseif e.element_type == Cairo.CAIRO_PATH_CLOSE_PATH
          closepath()
      else
          error("unknown CairoPathEntry " * repr(e.element_type))
          error("unknown CairoPathEntry " * repr(e.points))
      end
  end
Luxor.getpathflatMethod
getpathflat()

Get the current path, like getpath() but flattened so that there are no Bèzier curves.

Returns a CairoPath which is an array of element_type and points objects.

Luxor.getrotationMethod
getrotation(R::Matrix)
getrotation()

Get the rotation of a Julia 3x3 matrix, or the current Luxor rotation.

\[\begin{bmatrix} a & b & tx \\ c & d & ty \\ 0 & 0 & 1 \\ \end{bmatrix}\]

The rotation angle is atan(-b, a) or atan(c, d).

Luxor.getscaleMethod
getscale(R::Matrix)
getscale()

Get the current scale of a Julia 3x3 matrix, or the current Luxor scale.

Returns a tuple of x and y values.

Luxor.gettranslationMethod
gettranslation(R::Matrix)
gettranslation()

Get the current translation of a Julia 3x3 matrix, or the current Luxor translation.

Returns a tuple of x and y values.

Luxor.getworldpositionFunction
getworldposition(pt::Point = O;
    centered=true)

Return the world coordinates of pt.

The default coordinate system for Luxor/Cairo is that the top left corner is 0/0. If you use origin(), everything moves to the center of the drawing, and this function with the default centered option assumes an origin() function. If you choose centered=false, the returned coordinates will be relative to the top left corner of the drawing.

origin()
translate(120, 120)
@show currentpoint()      # => Point(0.0, 0.0)
@show getworldposition()  # => Point(120.0, 120.0)
Luxor.grestoreMethod
grestore()

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

Luxor.gsaveMethod
gsave()

Save the current color settings on the stack.

Luxor.hascurrentpointMethod
hascurrentpoint()

Return true if there is a current point. Obtain the current point with currentpoint().

Luxor.highestaspectratioMethod
highestaspectratio()

Find the highest aspect ratio of a list of rectangles, given the length of the side along which they are to be laid out.

Luxor.highlightcellsFunction
highlightcells(t::Table, cellnumbers, action::Symbol=:stroke;
        color::Colorant=colorant"red",
        offset = 0)

Highlight (draw or fill) one or more cells of table t. cellnumbers is a range, array, or an array of row/column tuples.

highlightcells(t, 1:10, :fill, color=colorant"blue")
highlightcells(t, vcat(1:5, 150), :stroke, color=colorant"magenta")
highlightcells(t, [(4, 5), (3, 6)])
Luxor.hypotrochoidFunction
hypotrochoid(R, r, d, action=:none;
        stepby=0.01,
        period=0.0,
        vertices=false)

Make a hypotrochoid with short line segments. (Like a Spirograph.) The curve is traced by a point attached to a circle of radius r rolling around the inside of a fixed circle of radius R, where the point is a distance d from the center of the interior circle. Things get interesting if you supply non-integral values.

Special cases include the hypocycloid, if d = r, and an ellipse, if R = 2r.

stepby, the angular step value, controls the amount of detail, ie the smoothness of the polygon,

If period is not supplied, or 0, the lowest period is calculated for you.

The function can return a polygon (a list of points), or draw the points directly using the supplied action. If the points are drawn, the function returns a tuple showing how many points were drawn and what the period was (as a multiple of pi).

Luxor.image_as_matrix!Method
image_as_matrix!(buffer)

Like image_as_matrix(), but use an existing UInt32 buffer.

buffer is a buffer of UInt32.

w = 200
h = 150
buffer = zeros(UInt32, w, h)
Drawing(w, h, :image)
origin()
juliacircles(50)
m = image_as_matrix!(buffer)
finish()
# collect(m)) is Array{ARGB32,2}
Images.RGB.(m)
Luxor.image_as_matrixMethod
image_as_matrix()

Return an Array of the current state of the picture as an array of ARGB32.

A matrix 50 wide and 30 high => a table 30 rows by 50 cols

using Luxor, Images

Drawing(50, 50, :png)
origin()
background(randomhue()...)
sethue("white")
fontsize(40)
fontface("Georgia")
text("42", halign=:center, valign=:middle)
mat = image_as_matrix()
finish()
Luxor.initnoiseMethod
initnoise(seed::Int)
initnoise()

Initialize the noise generation code.

julia> initnoise(); noise(1)
0.7453148982810598

julia> initnoise(); noise(1)
0.7027617067916981

If you provide an integer seed, it will be used to seed Random.seed!()` when the noise code is initialized:

julia> initnoise(41); noise(1) # yesterday
0.7134000046640385

julia> initnoise(41); noise(1) # today
0.7134000046640385

If you need to control which type of random number generator is used, you can provide your own and it will be used instead of the default Julia implementation.

julia> rng = MersenneTwister(1234) # any AbstractRNG
julia> initnoise(rng)
Luxor.insertvertices!Method
insertvertices!(pgon;
    ratio=0.5)

Insert a new vertex into each edge of a polygon pgon. The default ratio of 0.5 divides the original edge of the polygon into half.

Luxor.intersectboundingboxesMethod
intersectboundingboxes(bb1::BoundingBox, bb2::BoundingBox)

Return a BoundingBox that's an intersection of the two bounding boxes.

Luxor.intersection2circlesMethod
intersection2circles(pt1, r1, pt2, r2)

Find the area of intersection between two circles, the first centered at pt1 with radius r1, the second centered at pt2 with radius r2.

If one circle is entirely within another, that circle's area is returned.

Luxor.intersectioncirclecircleMethod
intersectioncirclecircle(cp1, r1, cp2, r2)

Find the two points where two circles intersect, if they do. The first circle is centered at cp1 with radius r1, and the second is centered at cp1 with radius r1.

Returns

(flag, ip1, ip2)

where flag is a Boolean true if the circles intersect at the points ip1 and ip2. If the circles don't intersect at all, or one is completely inside the other, flag is false and the points are both Point(0, 0).

Use intersection2circles() to find the area of two overlapping circles.

In the pure world of maths, it must be possible that two circles 'kissing' only have a single intersection point. At present, this unromantic function reports that two kissing circles have no intersection points.

Luxor.intersectionlinecircleMethod
intersectionlinecircle(p1::Point, p2::Point, cpoint::Point, r)

Find the intersection points of a line (extended through points p1 and p2) and a circle.

Return a tuple of (n, pt1, pt2)

where

  • n is the number of intersections, 0, 1, or 2
  • pt1 is first intersection point, or Point(0, 0) if none
  • pt2 is the second intersection point, or Point(0, 0) if none

The calculated intersection points won't necessarily lie on the line segment between p1 and p2.

Luxor.intersectionlinesMethod
intersectionlines(p0, p1, p2, p3,
    crossingonly=false)

Find point where two lines intersect.

If crossingonly == true the point of intersection must lie on both lines.

If crossingonly == false the point of intersection can be where the lines meet if extended almost to 'infinity'.

Accordng to this function, collinear, overlapping, and parallel lines never intersect. Ie, the line segments might be collinear but have no points in common, or the lines segments might be collinear and have many points in common, or the line segments might be collinear and one is entirely contained within the other.

If the lines are collinear and share a point in common, that is the intersection point.

Luxor.intersectlinepolyMethod
intersectlinepoly(pt1::Point, pt2::Point, C)

Return an array of the points where a line between pt1 and pt2 crosses polygon C.

Luxor.isarcclockwiseMethod
isarcclockwise(c::Point, A::Point, B::Point)

Return true if an arc centered at c going from A to B is clockwise.

If c, A, and B are collinear, then a hemispherical arc could be either clockwise or not.

Luxor.isinsideMethod
isinside(p::Point, bb:BoundingBox)

Returns true if pt is inside bounding box bb.

Luxor.isinsideMethod
isinside(p, pol; allowonedge=false)

Is a point p inside a polygon pol? Returns true if it does, or false.

This is an implementation of the Hormann-Agathos (2001) Point in Polygon algorithm.

The classification of points lying on the edges of the target polygon, or coincident with its vertices is not clearly defined, due to rounding errors or arithmetical inadequacy. By default these will generate errors, but you can suppress these by setting allowonedge to true.

Luxor.ispointonlineMethod
ispointonline(pt::Point, pt1::Point, pt2::Point;
    extended = false,
    atol = 10E-5)

Return true if the point pt lies on a straight line between pt1 and pt2.

If extended is false (the default) the point must lie on the line segment between pt1 and pt2. If extended is true, the point lies on the line if extended in either direction.

Luxor.ispointonpolyMethod
ispointonpoly(pt::Point, pgon;
    atol=10E-5)

Return true if pt lies on the polygon pgon.

Luxor.ispolyclockwiseMethod
ispolyclockwise(pgon)

Returns true if polygon is clockwise. WHEN VIEWED IN A LUXOR DRAWING...?

TODO This code is still experimental...

Luxor.ispolyconvexMethod
ispolyconvex(pts)

Return true if polygon is convex. This tests that every interior angle is less than or equal to 180°.

Luxor.juliacirclesFunction
juliacircles(radius=100;
    outercircleratio=0.75,
    innercircleratio=0.65,
    action=:fill)

Draw the three Julia circles ("dots") in color centered at the origin.

The distance of the centers of each circle from the origin is radius.

The optional keyword argument outercircleratio (default 0.75) determines the radius of each circle relative to the main radius. So the default is to draw circles of radius 75 points around a larger circle of radius 100.

Return the three centerpoints.

The innercircleratio (default 0.65) no longer does anything useful (it used to draw the smaller circles) and will be deprecated.

Luxor.julialogoMethod
julialogo(;
    action=:fill,
    color=true,
    bodycolor=colorant"black",
    centered=false)

Draw the Julia logo. The default action is to fill the logo and use the colors:

julialogo()

If color is false, the bodycolor color is used for the logo.

The function uses the current drawing state (position, scale, etc).

The centered keyword lets you center the logo at its mathematical center, but the optical center might lie somewhere else - it's difficult to position well due to its asymmetric design.

To use the logo as a clipping mask:

julialogo(action=:clip)

(In this case the color setting is automatically ignored.)

To obtain a stroked (outlined) version:

julialogo(action=:path)
sethue("red")
strokepath()
Luxor.juliatocairomatrixMethod
juliatocairomatrix(c)

Return a six-element matrix that's the equivalent of the 3x3 Julia matrix in c.

Luxor.labelFunction
label(txt::AbstractString, rotation::Float64, pos::Point=O;
    offset=5,
    leader=false,
    leaderoffsets=[0.0, 1.0])

Add a text label at a point, positioned relative to that point, for example, 0.0 is East, pi is West.

label("text", pi)          # positions text to the left of the origin
Luxor.labelFunction
label(txt::AbstractString, alignment::Symbol=:N, pos::Point=O;
    offset=5,
    leader=false,
    leaderoffsets=[0.0, 1.0])

Add a text label at a point, positioned relative to that point, for example, :N signifies North and places the text directly above that point.

Use one of :N, :S, :E, :W, :NE, :SE, :SW, :NW to position the label relative to that point.

label("text")          # positions text at North (above), relative to the origin
label("text", :S)      # positions text at South (below), relative to the origin
label("text", :S, pt)  # positions text South of pt
label("text", :N, pt, offset=20)  # positions text North of pt, offset by 20

The default offset is 5 units.

If leader is true, draw a line as well.

leaderoffsts uses normalized fractions (see between()) to specify the gap between the designated points and the start and end of the lines.

TODO: Negative offsets don't give good results.

Luxor.layoutMethod
layout(A, x, y, w, h)

From A, make a row of tiles (if wider than tall) or a column of tiles (if taller than wide).

Luxor.layout_springMethod
layout_spring(adjmatrix::Array{T,2} where T;
    densityconstant = 2.0,
    maxiterations = 100,
    initialtemperature = 2.0,
    boundingbox=BoundingBox(O - (250, 250), O + (250, 250)))
Luxor.layout_treeMethod

layout_tree(adjlist)

adj_list must not be cyclic, otherwise welcome to stack overflow...

Luxor.layoutgraphMethod
layoutgraph(adj_matrix::Array{T,2}; kwargs...)       <--
layoutgraph(adj_list::AbstractVector; kwargs...)     not yet implemented

Run one of two graph layout algorithms.

1: Layout the graph in adjacency matrix adj_matrix, using a spring-based method. Returns a tuple of ([xcoords], [ycoords]), which are scaled according to the supplied BoundingBox.

Keyword arguments

Lay out the graph in adjmatrix using the spring/repulsion model of Fruchterman and Reingold (1991). Returns a tuple of ([xcoords], [ycoords]).

Arguments

am An adjacency matrix of some type. Non-zero of the eltype of the matrix is used to determine if a link exists, but currently no sense of magnitude

densityconstant Constant to adjust the density of resulting layout. The default value is 2.0.

maxiterations how many iterations for applying the forces

initialtemperature the initial temperature controls movement per iteration. The default value is 2.0. Each iteration uses a lower temperature (TEMP = initialtemperature / iter). The idea is that the displacements of vertices are limited to some maximum temperature value, and this decreases over time. As the layout becomes better, the amount of adjustment becomes smaller.

locs_x the starting x values, between -1 and 1. The default values are randomly selected.

locs_y the starting y values, between -1 and 1. The default values are randomly selected.

boundingbox The Luxor BoundingBox into which the graph's coordinates will fit. Defaults to 500 × 500.

Luxor.lineFunction
line(pt1::Point, pt2::Point, action=:none)

Make a line between two points, pt1 and pt2 and do an action.

Luxor.lineMethod
line(pt)

Draw a line from the current position to the pt.

Luxor.makebezierpathMethod
makebezierpath(pgon::Array{Point, 1}; smoothing=1.0)

Return a Bézier path (a BezierPath) that represents a polygon (an array of points). The Bézier path is an array of segments (tuples of 4 points); each segment contains the four points that make up a section of the entire Bézier path. smoothing determines how closely the curve follows the polygon. A value of 0 returns a straight-sided path; as values move above 1 the paths deviate further from the original polygon's edges.

Luxor.maskMethod
mask(point::Point, focus::Point, width, height)
    max = 1.0,
    min = 0.0,
    easingfunction = easingflat)

Calculate a value between 0 and 1 for a point relative to a rectangular area defined by focus, width, and height. The value will approach max (1.0) at the center, and min (0.0) at the edges.

Luxor.maskMethod
mask(point::Point, focus::Point, radius)
    max = 1.0,
    min = 0.0,
    easingfunction = easingflat)

Calculate a value between 0 and 1 for a point relative to a circular area defined by focus and radius. The value will approach max (1.0) at the center of the circular area, and min (0.0) at the circumference.

Luxor.meshFunction
mesh(bezierpath::BezierPath,
     colors=Array{Colors.Colorant, 1})

Create a mesh. The first three or four elements of the supplied bezierpath define the three or four sides of the mesh shape.

The colors array define the color of each corner point. Colors are reused if necessary. At least one color should be supplied.

Use setmesh() to select the mesh, which will be used to fill shapes.

Example

@svg begin
    bp = makebezierpath(ngon(O, 50, 4, 0, vertices=true))
    mesh1 = mesh(bp, [
        "red",
        Colors.RGB(0, 1, 0),
        Colors.RGB(0, 1, 1),
        Colors.RGB(1, 0, 1)
        ])
    setmesh(mesh1)
    box(O, 500, 500, :fill)
end
Luxor.meshFunction
mesh(points::Array{Point},
     colors=Array{Colors.Colorant, 1})

Create a mesh.

The first three or four sides of the supplied points polygon define the three or four sides of the mesh shape.

The colors array define the color of each corner point. Colors are reused if necessary. At least one color should be supplied.

Example

@svg begin
    pl = ngon(O, 250, 3, pi/6, vertices=true)
    mesh1 = mesh(pl, [
        "purple",
        Colors.RGBA(0.0, 1.0, 0.5, 0.5),
        "yellow"
        ])
    setmesh(mesh1)
    setline(180)
    ngon(O, 250, 3, pi/6, :strokepreserve)
    setline(5)
    sethue("black")
    strokepath()
end
Luxor.midpointFunction
midpoint(bb::BoundingBox=BoundingBox())

Returns the point midway between the two points of the BoundingBox. This should also be the center, unless I've been very stupid...

Luxor.midpointMethod
midpoint(a)

Find midpoint between the first two elements of an array of points.

Luxor.midpointMethod
midpoint(p1, p2)

Find the midpoint between two points.

Luxor.nearestindexMethod
nearestindex(polydistancearray, value)

Return a tuple of the index of the largest value in polydistancearray less than value, and the difference value. Array is assumed to be sorted.

(Designed for use with polydistances()).

Luxor.newpathMethod
newpath()

Create a new path, after clearing the current path. After this there's no path and no current point.

Luxor.newsubpathMethod
newsubpath()

Start a new subpath, keeping the current path. After this there's no current point.

Luxor.nextgridpointMethod
nextgridpoint(g::GridHex)

Returns the next available grid point of a hexagonal grid.

Luxor.nextgridpointMethod
nextgridpoint(g::GridRect)

Returns the next available (or even the first) grid point of a grid.

Luxor.ngonFunction
ngon(centerpos, radius, sides=5, orientation=0, action=:none;
    vertices=false,
    reversepath=false)

Draw a regular polygon centered at point centerpos.

Find the vertices of a regular n-sided polygon centered at x, y with circumradius radius.

The polygon is constructed counterclockwise, starting with the first vertex drawn below the positive x-axis.

If you just want the raw points, use keyword argument vertices=true, which returns the array of points. Compare:

ngon(0, 0, 4, 4, 0, vertices=true) # returns the polygon's points:

    4-element Array{Luxor.Point, 1}:
    Luxor.Point(2.4492935982947064e-16, 4.0)
    Luxor.Point(-4.0, 4.898587196589413e-16)
    Luxor.Point(-7.347880794884119e-16, -4.0)
    Luxor.Point(4.0, -9.797174393178826e-16)

whereas

ngon(0, 0, 4, 4, 0, :close) # draws a polygon
Luxor.ngonFunction
ngon(x, y, radius, sides=5, orientation=0, action=:none;
    vertices=false, reversepath=false)

Draw a regular polygon centered at point centerpos.

Luxor.ngonsideFunction
ngonside(centerpoint::Point, sidelength::Real, sides::Int=5, orientation=0,
    action=:none; kwargs...)

Draw a regular polygon centered at centerpoint with sides sides of length sidelength.

Luxor.noiseMethod
noise(x)          ; detail = 1, persistence = 1.0) # 1D
noise(x, y)       ; detail = 1, persistence = 1.0) # 2D
noise(x, y, z)    ; detail = 1, persistence = 1.0) # 3D
noise(x, y, z, w) ; detail = 1, persistence = 1.0) # 4D

Generate a noise value between 0.0 and 1.0 corresponding to the x, y, z, and w values. An x value on its own produces 1D noise, x and y make 2D noise, and so on.

The detail value is an integer (>= 1) specifying how many octaves of noise you want.

The persistence value, typically between 0.0 and 1.0, controls how quickly the amplitude diminishes for each successive octave for values of detail greater than 1.

Luxor.offsetlinesegmentMethod
offsetlinesegment(p1, p2, p3, d1, d2)

Given three points, find another 3 points that are offset by d1 at the start and d2 at the end.

Negative d values put the offset on the left.

Luxor.offsetpolyMethod

offsetpoly(plist, shape::Function)

Return a closed polygon that is offset from and encloses an polyline.

The incoming set of points plist is treated as an polyline, and another set of points is created, which form a closed polygon offset from the source poly.

This method for offsetpoly() treats the list of points as n vertices connected with n - 1 lines. (The other method offsetpoly(plist, d) treats the list of points as n vertices connected with n lines.)

The supplied function determines the width of the line. f(0, θ) gives the width at the start (the slope of the curve at that point is supplied in θ), f(1, θ) provides the width at the end, and f(n, θ) is the width of point n/l.

Examples

This example draws a tilde, with the ends starting at 20 (10 + 10) units wide, swelling to 50 (10 + 10 + 15 + 15) in the middle, as f(0.5) = 25.

f(x, θ) =  10 + 15sin(x * π)
sinecurve = [Point(50x, 50sin(x)) for x in -π:π/24:π]
pgon = offsetpoly(sinecurve, f)
poly(pgon, :fill)

This example enhances the vertical part of the curve, and thins the horizontal parts.

g(x, θ) = rescale(abs(sin(θ)), 0, 1, 0.1, 30)
sinecurve = [Point(50x, 50sin(x)) for x in -π:π/24:π]
pgon = offsetpoly(sinecurve, g)
poly(pgon, :fill)
Luxor.offsetpolyMethod
offsetpoly(plist;
    startoffset = 10,
    endoffset   = 10,
    easingfunction = lineartween)

Return a closed polygon that is offset from and encloses an open polygon.

The incoming set of points plist is treated as an open polygon, and another set of points is created, which form a polygon lying ...offset units away from the source poly.

This method for offsetpoly() treats the list of points as n vertices connected with n - 1 lines. It allows you to vary the offset from the start of the line to the end.

The other method offsetpoly(plist, d) treats the list of points as n vertices connected with n lines.

Extended help

This function accepts a keyword argument that allows you to control the offset using a function, using the easing functionality built in to Luxor. By default the function is lineartween(), so the offset changes linearly between the startoffset and the endoffset. The function:

f(a, b, c, d) = 2sin((a * π))

runs from 0 to 2 and back as a runs from 0 to 1. The offsets are scaled by this amount.

Luxor.offsetpolyMethod
offsetpoly(plist::Array{Point, 1}, d) where T<:Number

Return a polygon that is offset from a polygon by d units.

The incoming set of points plist is treated as a polygon, and another set of points is created, which form a polygon lying d units away from the source poly.

Polygon offsetting is a topic on which people have written PhD theses and published academic papers, so this short brain-dead routine will give good results for simple polygons up to a point (!). There are a number of issues to be aware of:

  • very short lines tend to make the algorithm 'flip' and produce larger lines

  • small polygons that are counterclockwise and larger offsets may make the new

polygon appear the wrong side of the original

  • very sharp vertices will produce even sharper offsets, as the calculated intersection point veers off to infinity

  • duplicated adjacent points might cause the routine to scratch its head and wonder how to draw a line parallel to them

Luxor.originMethod
origin(pt:Point)

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

Luxor.originMethod
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)),

Luxor.paintMethod
paint()

Paint the current clip region with the current settings.

Luxor.pathtobezierpathsMethod
pathtobezierpaths(
    ; flat=true)

Convert the current path (which may consist of one or more paths) to an array of Bezier paths. Each Bezier path is, in turn, an array of path segments. Each path segment is a tuple of four points. A straight line is converted to a Bezier segment in which the control points are set to be the same as the end points.

If flat is true, use getpathflat() rather than getpath().

Example

This code draws the Bezier segments and shows the control points as "handles", like a vector-editing program might.

@svg begin
    fontface("MyanmarMN-Bold")
    st = "goo"
    thefontsize = 100
    fontsize(thefontsize)
    sethue("red")
    fontsize(thefontsize)
    textpath(st)
    nbps = pathtobezierpaths()
    for nbp in nbps
        setline(.15)
        sethue("grey50")
        drawbezierpath(nbp, :stroke)
        for p in nbp
            sethue("red")
            circle(p[2], 0.16, :fill)
            circle(p[3], 0.16, :fill)
            line(p[2], p[1], :stroke)
            line(p[3], p[4], :stroke)
            if p[1] != p[4]
                sethue("black")
                circle(p[1], 0.26, :fill)
                circle(p[4], 0.26, :fill)
            end
        end
    end
end
Luxor.pathtopolyMethod
pathtopoly()

Convert the current path to an array of polygons.

Returns an array of polygons, corresponding to the paths and subpaths of the original path.

Luxor.perpendicularMethod
perpendicular(p1, p2, k)

Return a point p3 that is k units away from p1, such that a line p1 p3 is perpendicular to p1 p2.

Convention? to the right?

Luxor.perpendicularMethod
perpendicular(p1, p2)

Return two points p3 and p4 such that a line from p3 to p4 is perpendicular to a line from p1 to p2, the same length, and the lines intersect at their midpoints.

Luxor.pieFunction
pie(x, y, radius, startangle, endangle, action=:none)

Draw a pie shape centered at x/y. Angles start at the positive x-axis and are measured clockwise.

Luxor.pieFunction
pie(radius, startangle, endangle, action=:none)

Draw a pie shape centered at the origin

Luxor.pieMethod
pie(centerpoint, radius, startangle, endangle, action=:none)

Draw a pie shape centered at centerpoint.

Angles start at the positive x-axis and are measured clockwise.

Luxor.placeimageFunction
placeimage(matrix::AbstractMatrix{UInt32}, pos=O;
    alpha=1, centered=false)

Place an image matrix on the drawing at pos with opacity/transparency alpha.

Use keyword centered=true to place the center of the image at the position.

Luxor.placeimageFunction
placeimage(svgimg, pos=O; centered=false)

Place an SVG image stored in svgimg on the drawing at pos. Use readsvg() to read an SVG image from file, or from SVG code.

Use keyword centered=true to place the center of the image at the position.

Luxor.placeimageMethod
placeimage(img, pt::Point=O, alpha; centered=false)
placeimage(pngimg, xpos, ypos, alpha; centered=false)

Place a PNG image pngimg on the drawing at pt or Point(xpos, ypos) with opacity/transparency alpha. The image has been previously loaded using readpng().

Use keyword centered=true to place the center of the image at the position.

Luxor.placeimageMethod
placeimage(pngimg, pos=O; centered=false)
placeimage(pngimg, xpos, ypos; centered=false)

Place the PNG image on the drawing at pos, or (xpos/ypos). The image img has been previously read using readpng().

Use keyword centered=true to place the center of the image at the position.

Luxor.pointcircletangentMethod
pointcircletangent(point::Point, circlecenter::Point, circleradius)

Find the two points on a circle that lie on tangent lines passing through an external point.

If both points are O, the external point is inside the circle, and the result is (O, O).

Luxor.pointcrossesboundingboxMethod
pointcrossesboundingbox(pt, bbox::BoundingBox)

Find and return the point where a line from the center of bounding box bbox to point pt would, if continued, cross the edges of the box.

Luxor.pointinverseMethod
pointinverse(A::Point, centerpoint::Point, rad)

Find A′, the inverse of a point A with respect to a circle centerpoint/rad, such that:

distance(centerpoint, A) * distance(centerpoint, A′) == rad^2

Return (true, A′) or (false, A).

Luxor.pointlinedistanceMethod
pointlinedistance(p::Point, a::Point, b::Point)

Find the distance between a point p and a line between two points a and b.

Luxor.polarMethod
polar(r, theta)

Convert point in polar form (radius and angle) to a Point.

polar(10, pi/4)

produces

Luxor.Point(7.071067811865475, 7.0710678118654755)
Luxor.polyFunction
poly(bbox::BoundingBox, :action; kwargs...)

Make a polygon around the BoundingBox in bbox.

Luxor.polyFunction

Draw a polygon.

poly(pointlist::Array{Point, 1}, action = :none;
    close=false,
    reversepath=false)

A polygon is an Array of Points. By default poly() doesn't close or fill the polygon, to allow for clipping.

Luxor.polyareaMethod
polyarea(p::Array)

Find the area of a simple polygon. It works only for polygons that don't self-intersect. See also polyorientation().

Luxor.polycentroidMethod

Find the centroid of simple polygon.

polycentroid(pointlist)

Returns a point. This only works for simple (non-intersecting) polygons.

You could test the point using isinside().

Luxor.polycrossFunction
polycross(pt::Point, radius, npoints::Int, ratio=0.5, orientation=0.0, action=:none;
    splay       = 0.5,
    vertices    = false,
    reversepath = false)

Make a cross-shaped polygon with npoints arms to fit inside a circle of radius radius centered at pt.

ratio specifies the ratio of the two sides of each arm. splay makes the arms ... splayed.

Use vertices=true to return the vertices of the shape instead of drawing it.

(Adapted from Compose.jl.xgon()))

Luxor.polydistancesMethod
polydistances(p::Array{Point, 1}; closed=true)

Return an array of the cumulative lengths of a polygon.

Luxor.polyfitFunction
polyfit(plist::Array, npoints=30)

Build a polygon that constructs a B-spine approximation to it. The resulting list of points makes a smooth path that runs between the first and last points.

Luxor.polyintersectMethod
polyintersect(p1::AbstractArray{Point, 1}, p2::AbstractArray{Point, 1};
    closed=true)

TODO: Fix/test/improve this experimental polygon intersection routine.

Return the points where polygon p1 and polygon p2 cross.

If closed is false, the intersection points must lie on the first n - 1 lines of each polygon.

Luxor.polyintersectionsMethod
polyintersections(S::Array{Point, 1}, C::Array{Point, 1})

Return an array of the points in polygon S plus the points where polygon S crosses polygon C. Calls intersectlinepoly().

TODO This code is experimental...

Luxor.polymove!Method
polymove!(pgon, frompoint::Point, topoint::Point)

Move (permanently) a polygon from frompoint to topoints.

Luxor.polyorientationMethod
polyorientation(pgon)

Returns a number which is positive if the polygon is clockwise in Luxor...

TODO This code is still experimental...

Luxor.polyperimeterMethod
polyperimeter(p::Array{Point, 1}; closed=true)

Find the total length of the sides of polygon p.

Luxor.polyportionFunction
polyportion(p::Array{Point, 1}, portion=0.5; closed=true, pdist=[])

Return a portion of a polygon, starting at a value between 0.0 (the beginning) and 1.0 (the end). 0.5 returns the first half of the polygon, 0.25 the first quarter, 0.75 the first three quarters, and so on.

Use closed=false to exclude the line joining the final point to the first point from the calculations.

If you already have a list of the distances between each point in the polygon (the "polydistances"), you can pass them in pdist, otherwise they'll be calculated afresh, using polydistances(p, closed=closed).

Use the complementary polyremainder() function to return the other part.

Luxor.polyreflect!Function
polyreflect!(pgon, pt1 = O, pt2 = O + (0, 100)

Reflect (permanently) a polygon in a line (default to the y-axis) joining two points.

Luxor.polyremainderFunction
polyremainder(p::Array{Point, 1}, portion=0.5; closed=true, pdist=[])

Return the rest of a polygon, starting at a value between 0.0 (the beginning) and 1.0 (the end). 0.5 returns the last half of the polygon, 0.25 the last three quarters, 0.75 the last quarter, and so on.

Use closed=false to exclude the line joining the final point to the first point from the calculations.

If you already have a list of the distances between each point in the polygon (the "polydistances"), you can pass them in pdist, otherwise they'll be calculated afresh, using polydistances(p, closed=closed).

Use the complementary polyportion() function to return the other part.

Luxor.polyremovecollinearpointsMethod
polyremovecollinearpoints(pgon::Array{Point, 1})

Return copy of polygon with no collinear points.

Caution: may return an empty polygon... !

TODO This code is still experimental...

Luxor.polyrotate!Method
polyrotate!(pgon, θ;
    center=O)

Rotate (permanently) a polygon around center by θ radians.

Luxor.polysampleMethod
polysample(p::Array{Point, 1}, npoints::T where T <: Integer;
        closed=true)

Sample the polygon p, returning a polygon with npoints to represent it. The first sampled point is:

 1/`npoints` * `perimeter of p`

away from the original first point of p.

If npoints is the same as length(p) the returned polygon is the same as the original, but the first point finishes up at the end (so new=circshift(old, 1)).

If closed is true, the entire polygon (including the edge joining the last point to the first point) is sampled.

Luxor.polyscale!Method
polyscale!(pgon, sh, sv;
    center=O)

Scale (permanently) a polygon by sh horizontally and sv vertically, relative to center.

Luxor.polyscale!Method
polyscale!(pgon, s;
   center=O)

Scale (permanently) a polygon by s, relative to center.

Luxor.polysmoothFunction
polysmooth(points, radius, action=:action; debug=false)

Make a closed path from the points and round the corners by making them arcs with the given radius. Execute the action when finished.

The arcs are sometimes different sizes: if the given radius is bigger than the length of the shortest side, the arc can't be drawn at its full radius and is therefore drawn as large as possible (as large as the shortest side allows).

The debug option also draws the construction circles at each corner.

Luxor.polysortbyangleFunction

Sort the points of a polygon into order. Points are sorted according to the angle they make with a specified point.

polysortbyangle(pointlist::Array, refpoint=minimum(pointlist))

The refpoint can be chosen, but the minimum point is usually OK too:

polysortbyangle(parray, polycentroid(parray))
Luxor.polysortbydistanceMethod

Sort a polygon by finding the nearest point to the starting point, then the nearest point to that, and so on.

polysortbydistance(p, starting::Point)

You can end up with convex (self-intersecting) polygons, unfortunately.

Luxor.polysplitMethod
polysplit(p, p1, p2)

Split a polygon into two where it intersects with a line. It returns two polygons:

(poly1, poly2)

This doesn't always work, of course. For example, a polygon the shape of the letter "E" might end up being divided into more than two parts.

Luxor.polytriangulateMethod
polytriangulate(plist::Array{Point,1}; epsilon = -0.01)

Triangulate the polygon in plist.

This uses the Bowyer–Watson/Delaunay algorithm to make triangles. It returns an array of triangular polygons.

TODO: This experimental polygon function is not very efficient, because it first copies the list of points (to avoid modifying the original), and sorts it, before making triangles.

Luxor.prettypolyFunction
prettypoly(bbox::BoundingBox, :action; kwargs...)

Make a decorated polygon around the BoundingBox in bbox. The vertices are in the order: bottom left, top left, top right, and bottom right.

Luxor.prettypolyFunction
prettypoly(points::Array{Point, 1}, action=:none, vertexfunction = () -> circle(O, 2, :stroke);
    close=false,
    reversepath=false,
    vertexlabels = (n, l) -> ()
    )

Draw the polygon defined by points, possibly closing and reversing it, using the current parameters, and then evaluate the vertexfunction function at every vertex of the polygon.

The default vertexfunction draws a 2 pt radius circle.

To mark each vertex of a polygon with a randomly colored filled circle:

p = star(O, 70, 7, 0.6, 0, vertices=true)
prettypoly(p, :fill, () ->
    begin
        randomhue()
        circle(O, 10, :fill)
    end,
    close=true)

The optional keyword argument vertexlabels lets you supply a function with two arguments that can access the current vertex number and the total number of vertices at each vertex. For example, you can label the vertices of a triangle "1 of 3", "2 of 3", and "3 of 3" using:

prettypoly(triangle, :stroke,
    vertexlabels = (n, l) -> (text(string(n, " of ", l))))
Luxor.previewMethod
preview()

If working in a notebook (eg 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.

Drawings of type :image should be converted to a matrix with image_as_matrix() before calling finish().

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, refer to COMSPEC.
Luxor.randomcolorMethod
randomcolor()

Set a random color. This may change the current alpha opacity too.

Luxor.randomhueMethod
randomhue()

Set a random hue, without changing the current alpha opacity.

Luxor.randompointMethod
randompoint(lowx, lowy, highx, highy)

Return a random point somewhere inside a rectangle defined by the four values.

Luxor.randompointMethod
randompoint(lowpt, highpt)

Return a random point somewhere inside the rectangle defined by the two points.

Luxor.randompointarrayMethod
randompointarray(lowx, lowy, highx, highy, n)

Return an array of n random points somewhere inside the rectangle defined by the four coordinates.

Luxor.randompointarrayMethod
randompointarray(w, h, d; attempts=20)

Return an array of randomly positioned points inside the rectangle defined by the current origin (0/0) and the width and height. d determines the minimum distance between each point. Increase attempts if you want the function to try harder to fill empty spaces; decrease it if it's taking too long to look for samples that work.

This uses Bridson's Poisson Disk Sampling algorithm: https://www.cs.ubc.ca/~rbridson/docs/bridson-siggraph07-poissondisk.pdf

Example

for pt in randompointarray(BoundingBox(), 20)
    randomhue()
    circle(pt, 10, :fill)
end
Luxor.randompointarrayMethod
randompointarray(bbox::BoundingBox, d; attempts=20)

Return an array of randomly positioned points inside the bounding box d units apart.

Luxor.randompointarrayMethod
randompointarray(lowpt, highpt, n)

Return an array of n random points somewhere inside the rectangle defined by two points.

Luxor.readpngMethod
readpng(pathname)

Read a PNG file.

This returns a image object suitable for placing on the current drawing with placeimage(). You can access its width and height fields:

image = readpng("test-image.png")
w = image.width
h = image.height
Luxor.readsvgMethod
readsvg(str)

Read an SVG image. str is either pathname or pure SVG code. This returns an SVG image object suitable for placing on the current drawing with placeimage().

Placing an SVG file:

@draw begin
    mycoollogo = readsvg("mylogo.svg")
    placeimage(mycoollogo)
end

Placing SVG code:

# from https://github.com/edent/SuperTinyIcons
julialogocode = """<svg xmlns="http://www.w3.org/2000/svg"
    aria-label="Julia" role="img"
    viewBox="0 0 512 512">
    <rect width="512" height="512" rx="15%" fill="#fff"/>
    <circle fill="#389826" cx="256" cy="137" r="83"/>
    <circle fill="#cb3c33" cx="145" cy="329" r="83"/>
    <circle fill="#9558b2" cx="367" cy="329" r="83"/>
</svg>"""

@draw begin
    julia_logo = readsvg(julialogocode)
    placeimage(julia_logo, centered=true)
end
Luxor.rectFunction
rect(cornerpoint, w, h, action;
    reversepath=false,
    vertices=false)

Create a rectangle with one corner at cornerpoint with width w and height h and do the action action.

Use vertices=true to return an array of the four corner points: bottom left, top left, top right, bottom right.

reversepath reverses the direction of the path (and returns points in the order: bottom left, bottom right, top right, top left).

Returns the four corner vertices.

Luxor.rectFunction
rect(xmin, ymin, w, h, action)

Create a rectangle with one corner at (xmin/ymin) with width w and height h and then do an action.

See box() for more ways to do similar things, such as supplying two opposite corners, placing by centerpoint and dimensions.

Luxor.rescaleFunction
rescale(x, from_min, from_max, to_min=0.0, to_max=1.0)

Convert x from one linear scale (from_min to from_max) to another (to_min to to_max).

The scales can also be supplied in tuple form:

rescale(x, (from_min, from_max), (to_min, to_max))
using Luxor
julia> rescale(15, 0, 100, 0, 1)
0.15

julia> rescale(15, (0, 100), (0, 1))
0.15

julia> rescale(pi/20, 0, 2pi, 0, 1)
0.025

julia> rescale(pi/20, (0, 2pi), (0, 1))
0.025

julia> rescale(25, 0, 1, 0, 1.609344)
40.2336

julia> rescale(15, (0, 100), (1000, 0))
850.0
Luxor.rlineMethod
rline(pt)

Draw a line relative to the current position to the pt.

Luxor.rmoveMethod
rmove(pt)

Move relative to current position by the pt's x and y:

Luxor.rotateMethod
rotate(a::Float64)

Rotate workspace by a radians clockwise (from positive x-axis to positive y-axis).

Luxor.rotationmatrixMethod
rotationmatrix(a)

Return a 3x3 Julia matrix that will apply a rotation through a radians.

Luxor.ruleFunction
rule(pos, theta;
    boundingbox=BoundingBox(),
    vertices=false)

Draw a straight line through pos at an angle theta from the x axis.

By default, the line spans the entire drawing, but you can supply a BoundingBox to change the extent of the line.

rule(O)       # draws an x axis
rule(O, pi/2) # draws a  y axis

The function:

rule(O, pi/2, boundingbox=BoundingBox()/2)

draws a line that spans a bounding box half the width and height of the drawing, and returns a Set of end points. If you just want the vertices and don't want to draw anything, use vertices=true.

Luxor.rulersMethod
rulers()

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

Luxor.scaleMethod
scale(x, y)

Scale workspace by x and y.

Example:

scale(0.2, 0.3)
Luxor.scaleMethod
scale(f)

Scale workspace by f in both x and y.

Luxor.scalingmatrixMethod
scalingmatrix(sx, sy)

Return a 3x3 Julia matrix that will apply a scaling by sx and sy.

Luxor.sectorFunction
sector(centerpoint::Point, innerradius, outerradius, startangle, endangle, action:none)

Draw an annular sector centered at centerpoint.

Luxor.sectorFunction
sector(centerpoint::Point, innerradius, outerradius, startangle, endangle,
    cornerradius, action:none)

Draw an annular sector with rounded corners, basically a bent sausage shape, centered at centerpoint.

TODO: The results aren't 100% accurate at the moment. There are small discontinuities where the curves join.

The cornerradius is reduced from the supplied value if neceesary to prevent overshoots.

Luxor.sectorFunction
sector(innerradius::Real, outerradius::Real, startangle::Real, endangle::Real,
   action::Symbol=:none)

Draw an annular sector centered at the origin.

Luxor.sectorFunction
sector(innerradius::Real, outerradius::Real, startangle::Real, endangle::Real,
   cornerradius::Real, action::Symbol=:none)

Draw an annular sector with rounded corners, centered at the current origin.

Luxor.setantialiasMethod
setantialias(n)

Set the current antialiasing to a value between 0 and 6:

antialias_default  = 0, the default antialiasing for the subsystem and target device
antialias_none     = 1, use a bilevel alpha mask
antialias_gray     = 2, use single-color antialiasing (using shades of gray for black text on a white background, for example)
antialias_subpixel = 3, take advantage of the order of subpixel elements on devices such as LCD panels
antialias_fast     = 4, perform some antialiasing but prefer speed over quality
antialias_good     = 5, balance quality against performance
antialias_best     = 6, render at the highest quality, sacrificing speed if necessary

This affects subsequent graphics, but not text, and it doesn't apply to all types of output file.

Luxor.setbezierhandlesMethod
setbezierhandles(bps::BezierPathSegment;
        angles  = [0.05, -0.1],
        handles = [0.3, 0.3])

Return a new Bezier path segment with new locations for the Bezier control points in the Bezier path segment bps.

angles are the two angles that the "handles" make with the line direciton.

handles are the lengths of the "handles". 0.3 is a typical value.

Luxor.setbezierhandlesMethod
setbezierhandles(bezpath::BezierPath;
        angles=[0 .05, -0.1],
        handles=[0.3, 0.3])

Return a new Bezierpath with new locations for the Bezier control points in every Bezier path segment of the BezierPath in bezpath.

angles are the two angles that the "handles" make with the line direciton.

handles are the lengths of the "handles". 0.3 is a typical value.

Luxor.setblendMethod
setblend(blend)

Start using the named blend for filling graphics.

This aligns the original coordinates of the blend definition with the current axes.

Luxor.setblendextendMethod
setblendextend(blend::Blend, mode)

Specify how color blend patterns are repeated/extended. Supply the blend and one of the following strings:

  • "repeat": the pattern is repeated

  • "reflect": the pattern is reflected (repeated in reverse)

  • "pad": outside the pattern, use the closest color

  • "none": outside of the pattern, use transparent pixels

Luxor.setcolorMethod
setcolor("gold")
setcolor("darkturquoise")

Set the current color to a named color. This use the definitions in Colors.jl to convert a string to RGBA eg setcolor("gold") or "green", "darkturquoise", "lavender", etc. The list is at Colors.color_names.

Use sethue() for changing colors without changing current opacity level.

sethue() and setcolor() return the three or four values that were used:

julia> setcolor(sethue("red")..., .8)

(1.0, 0.0, 0.0, 0.8)

julia> sethue(setcolor("red")[1:3]...)

(1.0, 0.0, 0.0)

You can also do:

using Colors
sethue(colorant"red")

See also setcolor.

Luxor.setcolorMethod
setcolor(r, g, b)
setcolor(r, g, b, alpha)
setcolor(color)
setcolor(col::Colors.Colorant)
setcolor(sethue("red")..., .2)

Set the current color.

Examples:

setcolor(convert(Colors.HSV, Colors.RGB(0.5, 1, 1)))
setcolor(.2, .3, .4, .5)
setcolor(convert(Colors.HSV, Colors.RGB(0.5, 1, 1)))

for i in 1:15:360
   setcolor(convert(Colors.RGB, Colors.HSV(i, 1, 1)))
   ...
end

See also sethue.

Luxor.setcolorMethod
setcolor((r, g, b, a))

Set the color to the tuple's values.

Luxor.setcolorMethod
setcolor((r, g, b))

Set the color to the tuple's values.

Luxor.setdashFunction
setdash(dashes::Vector, offset=0.0)

Set the dash pattern to the values in dashes. The first number is the length of the ink, the second the gap, and so on.

The offset specifies an offset into the pattern at which the stroke begins. So an offset of 10 means that the stroke starts at dashes[1] + 10 into the pattern.

Or use setdash("dot") etc.

Luxor.setdashMethod
setdash("dot")

Set the dash pattern to one of: "solid", "dotted", "dot", "dotdashed", "longdashed", "shortdashed", "dash", "dashed", "dotdotdashed", "dotdotdotdashed".

Use setdash(dashes::Vector) to specify the pattern numerically.

Luxor.setfontMethod
setfont(family, fontsize)

Select a font and specify the size.

Example:

setfont("Helvetica", 24)
settext("Hello in Helvetica 24 using the Pro API", Point(0, 10))
Luxor.setgrayMethod
setgray(n)
setgrey(n)

Set the color to a gray level of n, where n is between 0 and 1.

Luxor.sethueMethod
sethue("black")
sethue(0.3, 0.7, 0.9)
setcolor(sethue("red")..., .2)

Set the color without changing opacity.

sethue() is like setcolor(), but we sometimes want to change the current color without changing alpha/opacity. Using sethue() rather than setcolor() doesn't change the current alpha opacity.

See also setcolor.

Luxor.sethueMethod
sethue(0.3, 0.7, 0.9)

Set the color's r, g, b values. Use setcolor(r, g, b, a) to set transparent colors.

Luxor.sethueMethod
sethue(col::Colors.Colorant)

Set the color without changing the current alpha/opacity:

Luxor.sethueMethod
sethue((r, g, b, a))

Set the color to the tuple's values.

Luxor.sethueMethod
sethue((r, g, b))

Set the color to the tuple's values.

Luxor.setlinecapFunction
setlinecap(s)

Set the line ends. s can be "butt" or :butt (the default), "square" or :square, or "round" or :round.

Luxor.setlinejoinFunction
setlinejoin("miter")
setlinejoin("round")
setlinejoin("bevel")

Set the line join style, or how to render the junction of two lines when stroking.

Luxor.setmatrixMethod
setmatrix(m::Array)

Change the current matrix to matrix m. Use getmatrix() to get the current matrix.

Luxor.setmeshMethod
setmesh(mesh::Mesh)

Select a mesh, previously created with mesh(), for filling and stroking subsequent graphics.

Luxor.setmodeMethod
setmode(mode::AbstractString)

Set the compositing/blending mode. mode can be one of:

  • "clear" Where the second object is drawn, the first is completely removed.
  • "source" The second object is drawn as if nothing else were below.
  • "over" The default mode: like two transparent slides overlapping.
  • "in" The first object is removed completely, the second is only drawn where the first was.
  • "out" The second object is drawn only where the first one wasn't.
  • "atop" The first object is mostly intact, but mixes both objects in the overlapping area. The second object object is not drawn elsewhere.
  • "dest" Discard the second object completely.
  • "dest_over" Like "over" but draw second object below the first
  • "dest_in" Keep the first object whereever the second one overlaps.
  • "dest_out" The second object is used to reduce the visibility of the first where they overlap.
  • "dest_atop" Like "over" but draw second object below the first.
  • "xor" XOR where the objects overlap
  • "add" Add the overlapping areas together
  • "saturate" Increase Saturation where objects overlap
  • "multiply" Multiply where objects overlap
  • "screen" Input colors are complemented and multiplied, the product is complemented again. The result is at least as light as the lighter of the input colors.
  • "overlay" Multiplies or screens colors, depending on the lightness of the destination color.
  • "darken" Selects the darker of the color values in each component.
  • "lighten" Selects the lighter of the color values in each component.

See the Cairo documentation for details.

Luxor.setopacityMethod
setopacity(alpha)

Set the current opacity to a value between 0 and 1. This modifies the alpha value of the current color.

Luxor.setstrokescaleMethod
setstrokescale(state::Bool)

Enable/disable stroke scaling for the current drawing.

Luxor.settextMethod
settext(text, pos;
    halign = "left",
    valign = "bottom",
    angle  = 0, # degrees!
    markup = false)

settext(text;
    kwargs)

Draw the text at pos (if omitted defaults to 0/0). If no font is specified, on macOS the default font is Times Roman.

To align the text, use halign, one of "left", "center", or "right", and valign, one of "top", "center", or "bottom".

angle is the rotation - in counterclockwise degrees, rather than Luxor's default clockwise (+x-axis to +y-axis) radians.

If markup is true, then the string can contain some HTML-style markup. Supported tags include:

<b>, <i>, <s>, <sub>, <sup>, <small>, <big>, <u>, <tt>, and <span>

The <span> tag can contains things like this:

<span font='26' background='green' foreground='red'>unreadable text</span>
Luxor.shiftbezierhandlesMethod
shiftbezierhandles(bps::BezierPathSegment;
    angles=[0.1, -0.1], handles=[1.1, 1.1])

Return a new BezierPathSegment that modifies the Bezier path in bps by moving the control handles. The values in angles increase the angle of the handles; the values in handles modifies the lengths: 1 preserves the length, 0.5 halves the length of the handles, 2 doubles them.

Luxor.simplifyFunction

Simplify a polygon:

simplify(pointlist::Array, detail=0.1)

detail is the maximum approximation error of simplified polygon.

Luxor.slopeMethod
slope(pointA::Point, pointB::Point)

Find angle of a line starting at pointA and ending at pointB.

Return a value between 0 and 2pi. Value will be relative to the current axes.

slope(O, Point(0, 100)) |> rad2deg # y is positive down the page
90.0

slope(Point(0, 100), O) |> rad2deg
270.0

The slope isn't the same as the gradient. A vertical line going up has a slope of 3π/2.

Luxor.snapshotMethod
snapshot(;
    fname = :png,
    cb = missing,
    scalefactor = 1.0)

snapshot(fname, cb, scalefactor)
-> finished snapshot drawing, for display

Take a snapshot and save to 'fname' name and suffix. This requires that the current drawing is a recording surface. You can continue drawing on the same recording surface.

Arguments

fname the file name or symbol, see Drawing

cb crop box::BoundingBox - what's inside is copied to snapshot

scalefactor snapshot width/crop box width. Same for height.

Examples

snapshot()
snapshot(fname = "temp.png")
snaphot(fname = :svg)
cb = BoundingBox(Point(0, 0), Point(102.4, 96))
snapshot(cb = cb)
pngdrawing = snapshot(fname = "temp.png", cb = cb, scalefactor = 10)

The last example would return and also write a png drawing with 1024 x 960 pixels to storage.

Luxor.spiralFunction
spiral(a, b, action::Symbol=:none;
                 stepby = 0.01,
                 period = 4pi,
                 vertices = false,
                 log=false)

Make a spiral. The two primary parameters a and b determine the start radius, and the tightness.

For linear spirals (log=false), b values are:

lituus: -2

hyperbolic spiral: -1

Archimedes' spiral: 1

Fermat's spiral: 2

For logarithmic spirals (log=true):

golden spiral: b = ln(phi)/ (pi/2) (about 0.30)

Values of b around 0.1 produce tighter, staircase-like spirals.

Luxor.splittextMethod
splittext(s)

Split the text in string s into an array, but keep all the separators attached to the preceding word.

Luxor.squircleFunction
squircle(center::Point, hradius, vradius, action=:none;
    rt = 0.5, stepby = pi/40, vertices=false)

Make a squircle or superellipse (basically a rectangle with rounded corners). Specify the center position, horizontal radius (distance from center to a side), and vertical radius (distance from center to top or bottom):

The root (rt) option defaults to 0.5, and gives an intermediate shape. Values less than 0.5 make the shape more rectangular. Values above make the shape more round. The horizontal and vertical radii can be different.

Luxor.starFunction
star(center, radius, npoints=5, ratio=0.5, orientation=0, action=:none;
    vertices = false, reversepath=false)

Draw a star centered at a position:

Luxor.starFunction
star(xcenter, ycenter, radius, npoints=5, ratio=0.5, orientation=0, action=:none;
    vertices = false,
    reversepath=false)

Make a star. ratio specifies the height of the smaller radius of the star relative to the larger.

Returns the vertices of the star.

Use vertices=true to only return the vertices of a star instead of drawing it.

Luxor.strokepathMethod
strokepath()

Stroke the current path with the current line width, line join, line cap, dash, and stroke scaling settings. The current path is then cleared.

Luxor.strokepreserveMethod
strokepreserve()

Stroke the current path with current line width, line join, line cap, dash, and stroke scaling settings, but then keep the path current.

Luxor.svgstringMethod
svgstring()

Return the current and recently completed SVG drawing as a string of SVG commands.

Returns "" if there is no SVG information available.

To display the SVG string as a graphic, try the HTML() function in Base.

...
HTML(svgstring())

In a Pluto notebook, you can also display the SVG using:

# using PlutoUI
...
PlutoUI.Show(MIME"image/svg+xml"(), svgstring())

(This lets you right-click to save the SVG.)

Example

This example manipulates the raw SVG code representing the Julia logo:

Drawing(500, 500, :svg)
origin()
julialogo()
finish()
s = svgstring()
eachmatch(r"rgb.*?;", s) |> collect
    6-element Vector{RegexMatch}:
    RegexMatch("rgb(100%,100%,100%);")
    RegexMatch("rgb(0%,0%,0%);")
    RegexMatch("rgb(79.6%,23.5%,20%);")
    RegexMatch("rgb(25.1%,38.8%,84.7%);")
    RegexMatch("rgb(58.4%,34.5%,69.8%);")
    RegexMatch("rgb(22%,59.6%,14.9%);")
@drawsvg begin
    background("midnightblue")
    fontface("JuliaMono-Regular")
    fontsize(20)
    sethue("gold")
    text("JuliaMono: a monospaced font ", halign=:center)
    text("with reasonable Unicode support", O + (0, 22), halign=:center)
end 500 150
write("txt.svg", svgstring())
# minimize SVG
run(`svgo txt.svg -o txt-min.svg`)
Luxor.textMethod
text(str)
text(str, pos)
text(str, pos, angle=pi/2)
text(str, x, y)
text(str, pos, halign=:left)
text(str, valign=:baseline)
text(str, valign=:baseline, halign=:left)
text(str, pos, valign=:baseline, halign=:left)

Draw the text in the string str at x/y or pt, placing the start of the string at the point. If you omit the point, it's placed at the current 0/0.

angle specifies the rotation of the text relative to the current x-axis.

Horizontal alignment halign can be :left, :center, (also :centre) or :right. Vertical alignment valign can be :baseline, :top, :middle, or :bottom.

The default alignment is :left, :baseline.

This uses textextents() to query the dimensions of the text. This returns values of the built in to the font. You can't find

This uses Cairo's Toy text API.

Luxor.textboxFunction
textbox(lines::Array, pos::Point=O;
    leading = 12,
    linefunc::Function = (linenumber, linetext, startpos, height) -> (),
    alignment=:left)

Draw the strings in the array lines vertically downwards. leading controls the spacing between each line (default 12), and alignment determines the horizontal alignment (default :left).

Optionally, before each line, execute the function linefunc(linenumber, linetext, startpos, height).

Returns the position of what would have been the next line.

See also textwrap(), which modifies the text so that the lines fit into a specified width.

Luxor.textboxFunction
textbox(s::AbstractString, pos::Point=O;
    leading = 12,
    linefunc::Function = (linenumber, linetext, startpos, height) -> (),
    alignment=:left)
Luxor.textcurveFunction
textcurve(the_text, start_angle, start_radius, x_pos = 0, y_pos = 0;
      # optional keyword arguments:
      spiral_ring_step = 0,    # step out or in by this amount
      letter_spacing = 0,      # tracking/space between chars, tighter is (-), looser is (+)
      spiral_in_out_shift = 0, # + values go outwards, - values spiral inwards
      clockwise = true
      )

Place a string of text on a curve. It can spiral in or out.

start_angle is relative to +ve x-axis, arc/circle is centered on (x_pos,y_pos) with radius start_radius.

Luxor.textcurvecenteredMethod
textcurvecentered(the_text, the_angle, the_radius, center::Point;
      clockwise = true,
      letter_spacing = 0,
      baselineshift = 0

This version of the textcurve() function is designed for shorter text strings that need positioning around a circle. (A cheesy effect much beloved of hipster brands and retronauts.)

letter_spacing adjusts the tracking/space between chars, tighter is (-), looser is (+)). baselineshift moves the text up or down away from the baseline.

textcurvecentred (UK spelling) is a synonym

Luxor.textextentsMethod
textextents(str)

Return an array of six Float64s containing the measurements of the string str when set using the current font settings (Toy API):

1 x_bearing

2 y_bearing

3 width

4 height

5 x_advance

6 y_advance

The x and y bearings are the displacement from the reference point to the upper-left corner of the bounding box. It is often zero or a small positive value for x displacement, but can be negative x for characters like "j"; it's almost always a negative value for y displacement.

The width and height then describe the size of the bounding box. The advance takes you to the suggested reference point for the next letter. Note that bounding boxes for subsequent blocks of text can overlap if the bearing is negative, or the advance is smaller than the width would suggest.

Example:

textextents("R")

returns

[1.18652; -9.68335; 8.04199; 9.68335; 9.74927; 0.0]
Luxor.textlinesMethod
textlines(s::AbstractString, width::Real;
     rightgutter=5)

Split the text in s into lines up to width units wide (in the current font).

Returns an array of strings. Use textwrap to draw an array of strings.

TODO: A rightgutter optional keyword adds some padding to the right hand side of the column. This appears to be needed sometimes -— perhaps the algorithm needs improving to take account of the interaction of textextents and spaces?

Luxor.textoutlinesFunction
textoutlines(s::AbstractString, pos::Point=O, action::Symbol=:none;
    halign=:left,
    valign=:baseline,
    startnewpath=true)

Convert text to a graphic path and apply action.

By default this function discards any current path, unless you use startnewpath=false

See also textpath().

Luxor.textpathMethod
textpath(t)

Convert the text in string t and adds closed paths to the current path, for subsequent filling/stroking etc...

Typically you'll have to use pathtopoly() or getpath() or getpathflat() then work through the one or more path(s). Or use textoutlines().

Luxor.texttrackFunction
texttrack(txt, pos, tracking, fontsize=12;
    action=:fill,
    halign=:left,
    valign=:baseline,
    startnewpath=true)

Place the text in txt at pos, left-justified, and letter space ('track') the text using the value in tracking.

The tracking units depend on the current font size! 1 is 1/1000 em. In a 6‑point font, 1 em equals 6 points; in a 10‑point font, 1 em equals 10 points.

A value of -50 would tighten the letter spacing noticeably. A value of 50 would make the text more open.

The text drawing action applied to each character defaults to textoutlines(... :fill).

Luxor.textwrapMethod
textwrap(s::T where T<:AbstractString, width::Real, pos::Point;
    rightgutter=5,
    leading=0)
textwrap(s::T where T<:AbstractString, width::Real, pos::Point, linefunc::Function;
    rightgutter=5,
    leading=0)

Draw the string in s by splitting it at whitespace characters into lines, so that each line is no longer than width units. The text starts at pos such that the first line of text is drawn entirely below a line drawn horizontally through that position. Each line is aligned on the left side, below pos.

See also textbox().

Optionally, before each line, execute the function linefunc(linenumber, linetext, startpos, leading).

If you don't supply a value for leading, the font's built-in extents are used.

Text with no whitespace characters won't wrap. You can write a simple chunking function to split a string or array into chunks:

chunk(x, n) = [x[i:min(i+n-1,length(x))] for i in 1:n:length(x)]

For example:

textwrap(the_text, 300, boxtopleft(BoundingBox()) + 20,
    (ln, lt, sp, ht) -> begin
        c = count(t -> occursin(r"[[:punct:]]", t), split(lt, ""))
        @layer begin
            fontface("Menlo")
            sethue("darkred")
            text(string("[", c, "]"), sp + (310, 0))
        end
    end)

puts a count of the number of punctuation characters in each line at the end of the line.

Returns the position of what would have been the next line.

Luxor.ticklineMethod
tickline(startpos, finishpos;
    startnumber         = 0,
    finishnumber        = 1,
    major               = 1,
    minor               = 0,
    major_tick_function = nothing,
    minor_tick_function = nothing,
    rounding            = 2,
    axis                = true, # draw the line?
    log                 = false,
    vertices            = false # just return the points
    )

Draw a line with ticks. major is the number of ticks required between the start and finish point. So 1 divides the line in half. minor is the number of ticks between each major tick.

Examples

tickline(Point(0, 0), Point(100, 0))
tickline(Point(0, 0), Point(100, 0), major = 4)
majorticks, minorticks = tickline(Point(0, 0), Point(100, 0), axis=false)

Custom ticks

Supply functions to make custom ticks. Custom tick functions should have arguments as follows:

function mtick(n, pos;
        startnumber         = 0,
        finishnumber        = 1,
        nticks = 1)
        ...

and

function mntick(n, pos;
        startnumber        = 0,
        finishnumber       = 1,
        nticks             = 1,
        majorticklocations = [])
        ...

For example:

tickline(O - (300, 0), Point(300, 0),
    startnumber  = -10,
    finishnumber = 10,
    minor        = 0,
    major        = 4,
    axis         = false,
    major_tick_function = (n, pos;
        startnumber=30, finishnumber=40, nticks=10) -> begin
        @layer begin
            translate(pos)
            ticklength = get_fontsize()
            line(O, O + polar(ticklength, 3π/2), :stroke)
            k = rescale(n, 0, nticks - 1, startnumber, finishnumber)
            ticklength = get_fontsize() * 1.3
            text("$(round(k, digits=2))",
                O + (0, ticklength),
                halign=:center,
                valign=:middle,
                angle = -getrotation())
        end
    end)
Luxor.tidysvgMethod
tidysvg(fname)

Read the SVG image in fname and write it to a file fname-tidy.svg with modified glyph names.

Return the name of the modified file.

SVG images use named defs for text, which cause errors problem when used in a notebook. See for example.

A kludgy workround is to rename the elements...

Luxor.transformMethod
transform(a::Array)

Modify the current matrix by multiplying it by matrix a.

For example, to skew the current state by 45 degrees in x and move by 20 in y direction:

transform([1, 0, tand(45), 1, 0, 20])

Use getmatrix() to get the current matrix.

Luxor.translateMethod
translate(point)
translate(x::Real, y::Real)

Translate the workspace to x and y or to pt.

Luxor.translationmatrixMethod
translationmatrix(x, y)

Return a 3x3 Julia matrix that will apply a translation in x and y.

Luxor.trianglecenterMethod
trianglecenter(pt1::Point, pt2::Point, pt3::Point)

Return the centroid of the triangle defined by pt1, pt2, and pt3.

Luxor.trianglecircumcenterMethod
trianglecircumcenter(pt1::Point, pt2::Point, pt3::Point)

Return the circumcenter of the triangle defined by pt1, pt2, and pt3. The circumcenter is the center of a circle that passes through the vertices of the triangle.

Luxor.triangleincenterMethod
triangleincenter(pt1::Point, pt2::Point, pt3::Point)

Return the incenter of the triangle defined by pt1, pt2, and pt3. The incenter is the center of a circle inscribed inside the triangle.

Luxor.triangleorthocenterMethod
triangleorthocenter(pt1::Point, pt2::Point, pt3::Point)

Return the orthocenter of the triangle defined by pt1, pt2, and pt3.

Luxor.unpremultiplyalphaMethod
unpremultiplyalpha(a)

Given an array of UInt32 values, divide each value by the alpha value. See alphadivide or reversing premultiplied alpha values.

Returns an array of arrays, where each array has four Float64 values.

In a premultiplied image array, a 50% transparent red pixel is stored as 0x80800000, rather than not 0x80ff0000. This function reverses the process, dividing each RGB value by the alpha value.

The highest two digits of each incoming element is interpreted as the alpha value.

unpremultiplyalpha([0x80800000])
 1-element Array{Array{Float64,1},1}:
 [1.0, 0.0, 0.0, 0.5019607843137255]

Notice the arithmetic errors introduced as 0x80 gets converted to 0.5019.

Luxor.AnimatedGifType

Wraps the location of an animated gif so that it can be displayed

Luxor.BezierPathType

BezierPath is an array of BezierPathSegments. segments is Vector{BezierPathSegment}.

Luxor.BezierPathSegmentType

BezierPathSegment is an array of four points:

p1 - start point cp1 - control point for start point cp2 - control point for finishpoint p2 - finish point

Luxor.BoundingBoxType

The BoundingBox type holds two Points, corner1 and corner2.

BoundingBox(;centered=true)     # the bounding box of the Drawing
BoundingBox(s::AbstractString)  # the bounding box of a text string
BoundingBox(pt::Array)          # the bounding box of a polygon

BoundingBox(;centered=true) returns a BoundingBox the same size and position as the current drawing, assuming the origin (0, 0) is at the center.

The centered option defaults to true, and assumes the drawing is currently centered. If false, the function assumes that the origin is at the top left of the drawing. So this function doesn't really work if the current matrix has been modified (by translate(), scale(), rotate() etc.)

Luxor.BoundingBoxMethod
BoundingBox(str::AbstractString)

Return a BoundingBox that just encloses a text string, given the current font selection. Uses the Toy text API (ie text()).

Luxor.BoundingBoxMethod
BoundingBox(tile::BoxmapTile)

Return a BoundingBox of a BoxmapTile (as created with boxmap()).

Luxor.BoundingBoxMethod
BoundingBox(pointlist::Array)

Return the BoundingBox of a polygon (array of points).

Luxor.DrawingType

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

Drawing(width=600, height=600, file="luxor-drawing.png")

Extended help

Drawing()

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

You can specify 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 picture 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().

Drawing(width, height, :image)

creates the drawing in an image buffer in memory. You can obtain the data as a matrix with image_as_matrix().

Drawing(width, height, :rec)

creates the drawing in a recording surface in memory. snapshot(fname, ...) to any file format and bounding box, or render as pixels with image_as_matrix().

Drawing(width, height, strokescale=true)

creates the drawing and enables stroke scaling (strokes will be scaled according to the current transformation). (Stroke scaling is disabled by default.)

Luxor.GridHexType
GridHex(startpoint, radius, width=1200.0, height=1200.0)

Define a hexagonal grid, to start at startpoint and proceed along the x-axis and then along the y-axis, radius is the radius of a circle that encloses each hexagon. The distance in x between the centers of successive hexagons is:

$\frac{\sqrt{(3)} radius}{2}$

To get the next point from the grid, use nextgridpoint(g::Grid).

When you run out of grid points, you'll wrap round and start again.

Luxor.GridRectType
GridRect(startpoint, xspacing, yspacing, width, height)

Define a rectangular grid, to start at startpoint and proceed along the x-axis in steps of xspacing, then along the y-axis in steps of yspacing.

GridRect(startpoint, xspacing=100.0, yspacing=100.0, width=1200.0, height=1200.0)

For a column, set the xspacing to 0:

grid = GridRect(O, 0, 40)

To get points from the grid, use nextgridpoint(g::Grid).

julia> grid = GridRect(O, 0, 40);
julia> nextgridpoint(grid)
Luxor.Point(0.0, 0.0)

julia> nextgridpoint(grid)
Luxor.Point(0.0, 40.0)

When you run out of grid points, you'll wrap round and start again.

Luxor.MovieType

The Movie and Scene types and the animate() function are designed to help you create the frames that can be used to make an animated GIF or movie.

1 Provide width, height, title, and optionally a frame range to the Movie constructor:

demo = Movie(400, 400, "test", 1:500)

2 Define one or more scenes and scene-drawing functions.

3 Run the animate() function, calling those scenes.

Example

bang = Movie(400, 100, "bang")

backdrop(scene, framenumber) =  background("black")

function frame1(scene, framenumber)
    background("white")
    sethue("black")
    eased_n = scene.easingfunction(framenumber, 0, 1, scene.framerange.stop)
    circle(O, 40 * eased_n, :fill)
end

animate(bang, [
    Scene(bang, backdrop, 0:200),
    Scene(bang, frame1, 0:200, easingfunction=easeinsine)],
    creategif=true,
    pathname="/tmp/animationtest.gif")
Luxor.MovieMethod
Movie(width, height, movietitle)

Define a movie, specifying the width, height, and a title. The title will be used to make the output file name. The range defaults to 1:250.

Luxor.PartitionType
p = Partition(areawidth, areaheight, tilewidth, tileheight)

A Partition is an iterator that, for each iteration, returns a tuple of:

  • the x/y point of the center of each tile in a set of tiles that divide up a

rectangular space such as a page into rows and columns (relative to current 0/0)

  • the number of the tile

areawidth and areaheight are the dimensions of the area to be tiled, tilewidth/tileheight are the dimensions of the tiles.

Tiler and Partition are similar:

  • Partition lets you specify the width and height of a cell

  • Tiler lets you specify how many rows and columns of cells you want, and a margin

tiles = Partition(1200, 1200, 30, 30)
for (pos, n) in tiles
    # the point pos is the center of the tile
end

You can access the calculated tile width and height like this:

tiles = Partition(1200, 1200, 30, 30)
for (pos, n) in tiles
    ellipse(pos.x, pos.y, tiles.tilewidth, tiles.tileheight, :fill)
end

It's sometimes useful to know which row and column you're currently on:

tiles.currentrow
tiles.currentcol

should have that information for you.

Unless the tilewidth and tileheight are exact multiples of the area width and height, you'll see a border at the right and bottom where the tiles won't fit.

Luxor.PointType

The Point type holds two coordinates. It's immutable, you can't change the values of the x and y values directly.

Luxor.SceneType

The Scene type defines a function to be used to render a range of frames in a movie.

  • the movie created by Movie()
  • the framefunction is a function taking two arguments: the scene and the framenumber.
  • the framerange determines which frames are processed by the function. Defaults to the entire movie.
  • the optional easingfunction can be accessed by the framefunction to vary the transition speed
  • the optional opts which is a single argument of an abstract type which can be accessed within the framefunction
Luxor.SceneMethod
Scene(movie, function, range;
    easingfunction=easinoutquad,
    optarg=nothing)

Use the Scene() constructor function to create a scene. Supply a movie, a function to generate the scene, and a range of frames. Optionally you can supply an easing function, and other information, in optarg, which can be accessed as scene.opts.

Example

function initial(scene, framenumber)
    balls = scene.opts
    ...
end

animate(poolmovie, [
    Scene(poolmovie, initial, optarg=balls,   1:20),
    ...
    ])

To use an easing function inside the frame-generating function, you can create a normalized value with, for example:

eased_n = scene.easingfunction(framenumber, 0, 1, scene.framerange.stop)

Or, if the scene doesn't start at frame 1, calculate normalized easing function like this:

eased_n = scene.easingfunction(framenumber - scene.framerange.start,
    0, 1, scene.framerange.stop - scene.framerange.start)
Luxor.TableType
t = Table(nrows, ncols)
t = Table(nrows, ncols, colwidth, rowheight)
t = Table(rowheights, columnwidths)

Tables are centered at O, but you can supply a point after the specifications.

t = Table(nrows, ncols, centerpoint)
t = Table(nrows, ncols, colwidth, rowheight, centerpoint)
t = Table(rowheights, columnwidths, centerpoint)

Examples

Simple tables

t = Table(4, 3) # 4 rows and 3 cols, default is 100w, 50 h
t = Table(4, 3, 80, 30)   # 4 rows of 30pts high, 3 cols of 80pts wide
t = Table(4, 3, (80, 30)) # same
t = Table((4, 3), (80, 30)) # same

Specify row heights and column widths instead of quantities:

t = Table([60, 40, 100], 50) # 3 different height rows, 1 column 50 wide
t = Table([60, 40, 100], [100, 60, 40]) # 3 rows, 3 columns
t = Table(fill(30, (10)), [50, 50, 50]) # 10 rows 30 high, 3 columns 10 wide
t = Table(50, [60, 60, 60]) # just 1 row (50 high), 3 columns 60 wide
t = Table([50], [50]) # just 1 row, 1 column, both 50 units wide
t = Table(50, 50, 10, 5) # 50 rows, 50 columns, 10 units wide, 5 units high
t = Table([6, 11, 16, 21, 26, 31, 36, 41, 46], [6, 11, 16, 21, 26, 31, 36, 41, 46])
t = Table(15:5:55, vcat(5:2:15, 15:-2:5))
 #  table has 108 cells, with:
 #  row heights: 15 20 25 30 35 40 45 50 55
 #  col widths:  5 7 9 11 13 15 15 13 11 9 7 5
t = Table(vcat(5:10:60, 60:-10:5), vcat(5:10:60, 60:-10:5))
t = Table(vcat(5:10:60, 60:-10:5), 50) # 1 column 50 units wide
t = Table(vcat(5:10:60, 60:-10:5), 1:5:50)

A Table is an iterator that, for each iteration, returns a tuple of:

  • the x/y point of the center of cells arranged in rows and columns (relative to current 0/0)

  • the number of the cell (left to right, then top to bottom)

nrows/ncols are the number of rows and columns required.

It's sometimes useful to know which row and column you're currently on while iterating:

t.currentrow
t.currentcol

and row heights and column widths are available in:

t.rowheights
t.colwidths

box(t::Table, r, c) can be used to fill table cells:

@svg begin
    for (pt, n) in (t = Table(8, 3, 30, 15))
        randomhue()
        box(t, t.currentrow, t.currentcol, :fill)
        sethue("white")
        text(string(n), pt)
    end
end

or without iteration, using cellnumber:

@svg begin
    t = Table(8, 3, 30, 15)
    for n in eachindex(t)
        randomhue()
        box(t, n, :fill)
        sethue("white")
        text(string(n), t[n])
    end
end

To use a Table to make grid points:

julia> first.(collect(Table(10, 6)))
60-element Array{Luxor.Point,1}:
 Luxor.Point(-10.0, -18.0)
 Luxor.Point(-6.0, -18.0)
 Luxor.Point(-2.0, -18.0)
 ⋮
 Luxor.Point(2.0, 18.0)
 Luxor.Point(6.0, 18.0)
 Luxor.Point(10.0, 18.0)

which returns an array of points that are the center points of the cells in the table.

Luxor.TilerType
tiles = Tiler(areawidth, areaheight, nrows, ncols, margin=20)

A Tiler is an iterator that, for each iteration, returns a tuple of:

  • the x/y point of the center of each tile in a set of tiles that divide up a rectangular space such as a page into rows and columns (relative to current 0/0)

  • the number of the tile

areawidth and areaheight are the dimensions of the area to be tiled, nrows/ncols are the number of rows and columns required, and margin is applied to all four edges of the area before the function calculates the tile sizes required.

Tiler and Partition are similar:

  • Partition lets you specify the width and height of a cell

  • Tiler lets you specify how many rows and columns of cells you want, and a margin:

tiles = Tiler(1000, 800, 4, 5, margin=20)
for (pos, n) in tiles
    # the point pos is the center of the tile
end

You can access the calculated tile width and height like this:

tiles = Tiler(1000, 800, 4, 5, margin=20)
for (pos, n) in tiles
    ellipse(pos.x, pos.y, tiles.tilewidth, tiles.tileheight, :fill)
end

It's sometimes useful to know which row and column you're currently on. tiles.currentrow and tiles.currentcol should have that information for you.

To use a Tiler to make grid points:

first.(collect(Tiler(800, 800, 4, 4))

which returns an array of points that are the center points of the grid.

Luxor.TurtleType
Turtle()
Turtle(O)
Turtle(0, 0)
Turtle(O, pendown=true, orientation=0, pencolor=(1.0, 0.25, 0.25))

Create a Turtle. You can command a turtle to move and draw "turtle graphics".

The commands (unusually for Julia) start with a capital letter, and angles are specified in degrees.

Basic commands are Forward(), Turn(), Pendown(), Penup(), Pencolor(), Penwidth(), Circle(), Orientation(), Rectangle(), and Reposition().

Others include Push(), Pop(), Message(), HueShift(), Randomize_saturation(), Reposition(), and Pen_opacity_random().

Luxor.OConstant

O is a shortcut for the current origin, 0/0

Luxor.paper_sizesConstant
paper_sizes

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))