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
source
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.

source
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
source
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.

source
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)
source
Luxor.@layerMacro
The `layer` macro is a shortcut for `gsave()` ... `grestore()`.
source
Luxor.@pdfMacro
@pdf drawing-instructions [width] [height] [filename]

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

Examples

@pdf circle(O, 20, :fill)

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

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

@pdf circle(O, 20, :fill) 400 1200 "/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
source
Luxor.@pngMacro
@png drawing-instructions [width] [height] [filename]

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

Examples

@png circle(O, 20, :fill)

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

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

@png circle(O, 20, :fill) 400 1200 "/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
source
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]
@polar 10, pi/4

produces

Luxor.Point(7.0710678118654755, 7.071067811865475)
source
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.

THis example scans the generated SVG for color values:

s = @savesvg begin
    julialogo()
end

eachmatch(r"rgb.*?;", s) |> collect

5-element Vector{RegexMatch}:
 RegexMatch("rgb(0%,0%,0%);")
 RegexMatch("rgb(25.1%,38.8%,84.7%);")
 RegexMatch("rgb(22%,59.6%,14.9%);")
 RegexMatch("rgb(58.4%,34.5%,69.8%);")
 RegexMatch("rgb(79.6%,23.5%,20%);")
source
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
source
Base.:*Method

*(m::Matrix, pt::Point)

Transform a point pt by the 3×3 matrix m.

julia> M = [2 0 0; 0 2 0; 0 0 1]
3×3 Matrix{Int64}:
 2  0  0
 0  2  0
 0  0  1

julia> M * Point(20, 20)
Point(40.0, 40.0)

To convert between Cairo matrices (6-element Vector{Float64}) to a 3×3 Matrix, use cairotojuliamatrix() and juliatocairomatrix().

source
Base.convertMethod
convert(Point, bbox::BoundingBox)

Convert a BoundingBox to a four-point clockwise polygon.

convert(Vector{Point}, BoundingBox())
source
Base.hcatMethod
hcat(D::Drawing...; valign=:top, hpad=0, clip=true)

Create a new SVG drawing by horizontal concatenation of SVG drawings. If drawings have different height, the valign option can be used in order to define how to align. The hpad argument can be used to add padding between concatenated images.

The clip argument is a boolean for whether the concatenated images should be clipped before concatenation. Note that drawings sometimes have elements that go beyond it's margins, and they only show when the image is drawn in a larger canvas. The clip argument ensures that these elements are not drawn in the concatenated drawing.

Example:

d1 = Drawing(200, 100, :svg)
origin()
circle(O, 60, :fill)
finish()

d2 = Drawing(200, 200, :svg)
rect(O, 200, 200, :fill)
finish()
hcat(d1, d2; hpad = 10, valign = :top, clip = true)
source
Base.inMethod
in(pt, bbox::BoundingBox)

Test whether pt is inside bbox.

source
Base.isapproxMethod

isapprox(p1::Point, p2::Point; atol = 1e-6, kwargs...)

Compare points.

source
Base.isequalMethod

isequal(p1::Point, p2::Point) = isapprox(p1.x, p2.x, atol = 0.00000001) && (isapprox(p1.y, p2.y, atol = 0.00000001))

Compare points.

source
Base.randMethod
rand(bbox::BoundingBox)

Return a random Point that lies inside bbox.

source
Base.vcatMethod
vcat(D::Drawing...; halign=:left, vpad=0, clip=true)

Creates a new SVG drawing by vertical concatenation of SVG drawings. If drawings have different widths, the halign option can be used in order to define how to align. The vpad argument can be used to add padding between concatenated images.

The clip argument is a boolean for whether the concatenated images should be clipped before concatenation. Note that drawings sometimes have elements that go beyond it's margins, and they only show when the image is drawn in a larger canvas. The clip argument ensures that these elements are not drawn in the concatenated drawing.

Example:

d1 = Drawing(200, 100, :svg)
origin()
circle(O, 60, :fill)
finish()

d2 = Drawing(200, 200, :svg)
rect(O, 200, 200, :fill)
finish()
vcat(d1, d2; vpad = 10, halign = :left, clip = true)
source
Luxor.CircleFunction
Circle(t::Turtle, radius=1.0)

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

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

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

source
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.)

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

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

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

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

source
Luxor.PopMethod
Pop(t::Turtle)

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

source
Luxor.PushMethod
Push(t::Turtle)

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

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

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

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

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

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

Rotate the turtle to face towards a given point.

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

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

source
Luxor._adjust_background_rectsMethod
_adjust_background_rects(buffer::UInt8[]; addmarker = true)

See issue https://github.com/JuliaGraphics/Luxor.jl/issues/150 for discussion details.

Setting backgrounds in a recording surface (:rec) and creating a svg from it result in elements as

<rect x="0" y="0" width="16777215" height="16777215" .../>

independent of an existing transformation matrix (e.g. set with origin(...) or snapshot with a crop bounding box). An existing transformation matrix manifests in the svg file as

<use xlink:href="#surface199" transform="matrix(3 1 -1 3 30 40)"/>

which is applied to every element including the background rects. This transformation needs to be inversed for the background rects which is added in this function.

If addmarker is not set to false, a class property is set as marker:

<rect class="luxor_adjusted" x="0" y="0" width="16777215" height="16777215" .../>
source
Luxor._betweenpolyMethod
_betweenpoly(loop1, loop2, k;
    samples = 100,
    easingfunction = easingflat)

Find a simple polygon between the two simple polygons loop1 and loop2 corresponding to k, where 0.0 < k < 1.0.

By default, easingfunction = easingflat, so the intermediate steps are linearly spaced. If you use another easing function, intermediate steps are determined by the value of the easing function at k.

Used by polymorph().

Because polysample() can treat the polygon as open or closed (with different results), you can specify how the sampling is done here, with the closed= keyword:

  • closed = true -> polygons are sampled as closed

  • closed = false -> polygons are sampled as open

  • closed = (true, false) -> first polygon is sampled as closed, second as open

source
Luxor._drawing_indicesMethod
Luxor._drawing_indices()

Get a UnitRange over all available indices of drawings.

With Luxor you can work on multiple drawings simultaneously. Each drawing is stored in an internal array. The first drawing is stored at index 1 when you start a drawing with Drawing(...). To start a second drawing you call Luxor._set_next_drawing_index(), which returns the new index. Calling another Drawing(...) stores the second drawing at this new index. Luxor._set_next_drawing_index() will return and set the next available index which is available for a new drawing. This can be a new index at the end of drawings, or, if you already finished a drawing with finish(), the index of this finished drawing. To specify on which drawing the next graphics command should be applied you call Luxor._set_drawing_index(i). All successive Luxor commands work on this drawing. With Luxor._get_drawing_index() you get the current active drawing index.

Multiple drawings is especially helpful for interactive graphics with live windows like MiniFB.

Example:

using Luxor
Drawing(500, 500, "1.svg")
origin()
setcolor("red")
circle(Point(0, 0), 100, action = :fill)

Luxor._drawing_indices()               # returns 1:1

Luxor._get_next_drawing_index()        # returns 2 but doesn't change current drawing
Luxor._set_next_drawing_index()        # returns 2 and sets current drawing to it
Drawing(500, 500, "2.svg")
origin()
setcolor("green")
circle(Point(0, 0), 100, action = :fill)

Luxor._drawing_indices()               # returns 1:2
Luxor._set_drawing_index(1)            # returns 1

finish()
preview()                             # presents the red circle 1.svg

Luxor._drawing_indices()               # returns 1:2
Luxor._set_next_drawing_index()        # returns 1 because drawing 1 was finished before

Drawing(500, 500, "3.svg")
origin()
setcolor("blue")
circle(Point(0, 0), 100, action = :fill)

finish()
preview()                             # presents the blue circle 3.svg

Luxor._set_drawing_index(2)            # returns 2
finish()
preview()                             # presents the green circle 2.svg

Luxor._drawing_indices()               # returns 1:2, but all are finished
Luxor._set_drawing_index(1)            # returns 1

preview()                             # presents the blue circle 3.svg again

Luxor._set_drawing_index(10)           # returns 1 as 10 does not existing    
Luxor._get_drawing_index()             # returns 1
Luxor._get_next_drawing_index()        # returns 1, because 1 was finished
source
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)`.

source
Luxor._get_drawing_indexMethod
Luxor._get_drawing_index()

Returns the index of the current drawing. If there isn't any drawing yet returns 1.

source
Luxor._get_next_drawing_indexMethod
Luxor._get_next_drawing_index()

Returns the next available drawing index. This can either be a new index or an existing index where a finished (finish()) drawing was stored before.

source
Luxor._has_drawingMethod
Luxor._has_drawing()

Returns true if there is a current drawing available or finished, otherwise false.

source
Luxor._set_drawing_indexMethod
Luxor._set_drawing_index(i::Int)

Set the active drawing for successive graphic commands to index i if exist. if index i doesn't exist, the current drawing is unchanged.

Returns the current drawing index.

Example:

next_index=5
if Luxor._set_drawing_index(next_index) == next_index
    # do some additional graphics on the existing drawing
    ...
else
    @warn "Drawing "*string(next_index)*" doesn't exist"
endif
source
Luxor._set_next_drawing_indexMethod
Luxor._set_next_drawing_index()

Set the current drawing to the next available drawing index. This can either be a new index or an existing index where a finished (finish()) drawing was stored before.

Returns the current drawing index.

source
Luxor._split_string_into_head_mid_tailMethod
_split_string_into_head_mid_tail(s::String,id::String)

splits s into head, mid and tail string. Example:

  s="head...<g id="\$id">...</g>...tail"

results in

  head="head..."
  mid="<g id="\$id">...</g>"
  tail="...tail"
source
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.

source
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.

source
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()...))
source
Luxor.anglethreepointsMethod
anglethreepoints(p1::Point, p2::Point, p3::Point)

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

If the angle is less than π, the line heads to the left.

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

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

source
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.

If you prefer to use the FFMPEG package, use code such as this:

using FFMPEG

...

tempdirectory = "/tmp/temp/"

animate(movie, [
        Scene(movie, frame, 1:50)
    ], creategif=false, tempdirectory=tempdirectory)

FFMPEG.ffmpeg_exe(`-r 30 -f image2 -i $(tempdirectory)/%10d.png -c:v libx264 -r 30 -pix_fmt yuv420p -y /tmp/animation.mp4`)
source
Luxor.arcMethod
arc(centerpoint::Point, radius, angle1, angle2; action=:none)
arc(centerpoint::Point, radius, angle1, angle2, action)

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

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

See also: carc(), arc2r(), arc2sagitta(), ...

source
Luxor.arc2rMethod
  arc2r(c1::Point, p2::Point, p3::Point; action=:none)
  arc2r(c1::Point, p2::Point, p3::Point, action)

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.

source
Luxor.arc2sagittaMethod
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, and add it to the current path.

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

source
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])))
source
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
source
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.

source
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
source
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.

source
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)
background(Luxor.Colors.RGB(0, 1, 0))

If Colors.jl has been imported:

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.

source
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
source
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.

source
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).

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

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

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

Return the curvature of the Bézier 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).

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

Given four points, return the Bézier 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.

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

Given four points, return the Bézier curve that passes through all four points.

source
Luxor.bezierpathtopathMethod
bezierpathtopath(bp::BezierPath)

Convert a Bezier path to a Path object.

@draw drawpath(polytopath(ngon(O, 145, 5, vertices = true)), action = :fill)
source
Luxor.bezierpathtopolyMethod
bezierpathtopoly(bezierpath::BezierPath;
    steps=10)

Convert a Bézier path (an array of BezierPathSegments, where each is a tuple of four points: anchor1, control1, control2, anchor2), to a polygon.

To make a Bézier path, use makebezierpath() on a polygon.

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

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

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 that 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.

The function finds the interesction point of two lines with the two angles and constructs a BezierPathSegment that fits.

See also the bezierfrompoints() function that makes a BezierPathSegment that passes through four points.

Example

drawbezierpath(beziersegmentangles(O, O + (100, 0),
    out = deg2rad(45),
    in  = 2π - deg2rad(45)),
    :stroke)
source
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).

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

Convert a BezierPathsegment to a polygon (an array of points).

source
Luxor.bezier′Method

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

Return the first derivative of the Bézier function.

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

Return the second derivative of Bézier function.

source
Luxor.bezigonMethod
bezigon(pts::Array{Point,1}, sides;
    close = false,
    action = :none))

Construct a bezigon, a path made of Bezier curves.

corners is an array of points, the corners of the bezigon, eg this triangle:

[Point(0, 0), Point(50, 50), Point(100, 0)]

sides is an array of arrays of points, where each array contains two control points, eg:

    sides = [
        [Point(-10, -20), Point(40, -120)], # control points for first side
        [Point(120, -120), Point(180, -20)],
    ]

The first pair of sides (two points) are control points, which combine with the first two points in corners to define a Bezier curve. And so on for the next pair.

Returns a path.

source
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"
    )
source
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().

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

Create a linear blend.

Example:

redblue = blend(pos, pos, "red", "blue")
source
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().

source
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)
source
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)
source
Luxor.boundingboxesintersectMethod
boundingboxesintersect(bbox1::BoundingBox, bbox2::BoundingBox)
boundingboxesintersect(acorner1::Point, acorner2::Point, bcorner1::Point, bcorner2::Point)

Return true if the two bounding boxes intersect.

source
Luxor.boxMethod
box(points::Array; action=:none,
    reversepath=reversepath,
    vertices=vertices)
box(points::Array; action=:none,
    reversepath=reversepath,
    vertices=vertices)

Create a box/rectangle using the first two points of an array of Points to defined opposite corners, and add it to the current path. Then apply action.

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

source
Luxor.boxMethod
box(bbox::BoundingBox, cornerradii::Array;
    action = :none)

Make a box that is the size of the BoundingBox bbox with curved corners.

source
Luxor.boxMethod
box(bbox::BoundingBox, cornerradius::Real;
    action = :none)

Make a box that is the size of the BoundingBox bbox with curved corners.

source
Luxor.boxMethod
box(bbox::BoundingBox;
    action   = :none,
    vertices = false)
box(bbox::BoundingBox, action::Symbol;
    vertices=false)

Define 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.

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

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

Create boxmaps using boxmap().

source
Luxor.boxMethod
box(pt, width, height, cornerradii::Array; action=:none)
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
source
Luxor.boxMethod
box(pt, width, height, cornerradius, action=:none)
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.

source
Luxor.boxMethod
box(pt::Point, width, height; action=:none, vertices=false)
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 apply the action.

reversepath reverses the direction of the path.

source
Luxor.boxMethod
box(cornerpoint1, cornerpoint2; action=:none, vertices=false, reversepath=false)
box(cornerpoint1, cornerpoint2, action; vertices=false, reversepath=false)

Create a box (rectangle) between two points and add it to the current path. Then apply action.

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

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

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

Make box around cell cellnumber in table t.

source
Luxor.boxMethod
box(t::Table, r::Integer, c::Integer, action::Symbol)
box(t::Table, r::Integer, c::Integer; action=:none)

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

source
Luxor.boxMethod
box(tiles::Tiler, n::Integer; action=:none, vertices=false, reversepath=false)
box(tiles::Tiler, n::Integer, action::Symbol=:none; vertices=false, reversepath=false)

Draw a box in tile n of tiles tiles.

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

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

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

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

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

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

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

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

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

Return the length of the diagonal of bounding box bb.

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

Return the height of bounding box bb.

source
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"
source
Luxor.boxmiddlecenterFunction
boxmiddlecenter(bb::BoundingBox=BoundingBox())

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

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

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

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

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

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

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

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

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

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

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

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

Return the width of bounding box bb.

source
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.

source
Luxor.cairotojuliamatrixMethod
cairotojuliamatrix(c)

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

See getmatrix() for details.

source
Luxor.carcMethod
carc(centerpoint::Point, radius, angle1, angle2; action=:none)
carc(centerpoint::Point, radius, angle1, angle2, action)

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

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

See also: arc(), carc2r(), carc2sagitta(), ...

source
Luxor.carc2rMethod
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.

source
Luxor.carc2sagittaMethod
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, and add it to the current path.

Return tuple of center point and radius of arc.

source
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.

source
Luxor.circleMethod
circle(pt1::Point, pt2::Point, pt3::Point; action=:none)
circle(pt1::Point, pt2::Point, pt3::Point, action)

Make a circle that passes through three points, and add it to the current path.

source
Luxor.circleMethod
circle(pt1::Point, pt2::Point; action=:none)
circle(pt1::Point, pt2::Point, action)

Make a circle that passes through two points that define the diameter, and add it to the current path.

source
Luxor.circleMethod
circle(centerpoint::Point, r; action=:none)
circle(centerpoint::Point, r, action)

Make a circle of radius r centered at 'centerpoint', and add it to the current path.

action is one of the actions applied by do_action(), defaulting to :none.

Returns a tuple of two points, the corners of a bounding box that encloses the circle.

You can also use ellipse() to draw circles and place them by their centerpoint.

circlepath() builds a circle using Bézier curves, and add it to the current path.

squircle() builds a superellipse. polysuper() build a 'supershape', a generalization of the superellipse.

juliacircles() draws three circles in a familiar formation.

See also: arc(), arc2r(), arc2sagitta(), carc(), carc2r(), carc2sagitta(), circlering(), crescent(), curve(), spiral(), etc.

source
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.

source
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.

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

Make a circle using Bézier curves, and add it to the current path.

One benefit of using this rather than circle() is that you can use the reversepath option to draw the circle clockwise rather than circle's counterclockwise.

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

Return two points, the corners of a bounding box.

source
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.)

source
Luxor.circleringFunction
circlering(o_center, o_radius, count=3)

Find count circles that fit inside an imaginary circle centered at o_center with radius and are tangent to it.

Returns:

  • an array of center/radius tuples defining each of the circles
  • a single center/radius tuple defining the circle that fits inside the calculated circles
cs, ic = circlering(Point(0, 0), 200, 3)

(Tuple
    [(Point(-53.59246086870698, 92.82486512724742), 92.815078262586), 
     (Point(-53.59246086870705, -92.82486512724739), 92.815078262586), 
     (Point(107.184921737414, -2.6252734264516723e-14), 92.815078262586)
   ],
    (Point(0.0, 0.0), 14.369843474828002))

@draw begin
    cs, ic = circlering(Point(0, 0), 200, 3)
    map(c -> circle(first(c), last(c), :fill), cs)
    circle(first(ic), last(ic), :fill)
end
source
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.)

source
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(). [?]

source
Luxor.clippreserveMethod
clippreserve()

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

source
Luxor.closepathMethod
closepath()

Draw a line from the current point to the first point of the current subpath. This is Cairo's close_path() function.

source
Luxor.crescentMethod

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, and add it to the current path. 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) # or
crescent(O, 100, O + (60, 0), 150, action=:fill)
source
Luxor.crescentMethod
crescent(pos, innerradius, outeradius;
    action=nothing,
    vertices=false,
    reversepath=false,
    steps = 30)

Create a crescent-shaped polygon, aligned with the current x-axis, and add it to the current path. 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) # or
crescent(O, 130, 200, action=:fill)

Create a stroked crescent shape - the inner radius of 0 produces a semicircle - and add it to the current path.

crescent(O, 0, 200, :stroke) # or
crescent(O, 0, 200, action=:stroke)
source
Luxor.cropmarksMethod
cropmarks(center, width, height)

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

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

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

source
Luxor.currentdrawingMethod
currentdrawing(d::Drawing)

Sets and returns the current Luxor drawing overwriting an existing drawing if exists.

source
Luxor.currentpointMethod
currentpoint()

Return the current point. This is the most recent point in the current path, as defined by one of the path building functions such as move(), line(), curve(), arc(), rline(), and rmove().

To see if there is a current point, use hascurrentpoint().

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

Add a Bézier curve to the current path.

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

source
Luxor.determinant3Method
determinant3(p1::Point, p2::Point, p3::Point)

Find the determinant of the 3×3 matrix:

\[\begin{bmatrix} p1.x & p1.y & 1 \\ p2.x & p2.y & 1 \\ p3.x & p3.y & 1 \\ \end{bmatrix}\]

If the value is 0.0, the points are collinear.

source
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.

source
Luxor.distanceMethod
distance(h1::Hexagon, h2::Hexagon)

Find distance between hexagons h1 and h2.

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

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

source
Luxor.do_actionMethod
do_action(action)

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

The :path action adds the graphics to the current path.

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

Return the scalar dot product of the two points.

source
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)
source
Luxor.drawbezierpathMethod
drawbezierpath(bezierpath::BezierPath, action=:none;
    close=true)
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.

TODO Return something more useful than a Boolean.

source
Luxor.drawbezierpathMethod
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.

TODO Return something more useful than a Boolean.

source
Luxor.drawpathMethod
drawpath(path::Path, k::Real;
    steps=10, # used when approximating Bezier curve segments
    action=:none,
    startnewpath=true,
    pathlength = 0.0)

Draw the path in path starting at the beginning and stopping at k between 0 and 1. So if k is 0.5, half the path is drawn.

Returns the last point processed.

The function calculates the length of the entire path before drawing it. If you want to draw a large path more than once, it might be more efficient to calculate the length of the path first, and provide it to the pathlength keyword.

The steps parameter is used when approximating the length of any curve (Bezier) sections.

source
Luxor.drawpathMethod
drawpath(cp::Path; action=:none, startnewpath=true)
drawpath(cp::Path, action; startnewpath=true)

Make the Luxor path stored in cp and apply the action.

To make paths, follow some path construction functions such as move(), line(), and curve() with the storepath() function.

By default, startnewpath=true, which starts a new path, discarding any existing path contents.

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

exponential easing in - accelerating from zero velocity

source
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))
source
Luxor.easeinoutcircMethod
easeinoutcirc(t, b, c, d)

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

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

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

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

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

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

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

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

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

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

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

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

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

source
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
source
Luxor.ellipseMethod
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), and add it to the current path.

source
Luxor.ellipseMethod
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.

source
Luxor.ellipseMethod
ellipse(centerpoint::Point, w, h; action=:none)
ellipse(centerpoint::Point, w, h; action)

Make an ellipse, centered at centerpoint, with width w, and height h, and add it to the current path.

Returns a tuple of two points, the corners of a bounding box that encloses the ellipse.

See also: circle(), squircle(), ellipseinquad()...

source
Luxor.ellipseinquadMethod
ellipseinquad(qgon; action=:none)
ellipseinquad(qgon, action)

Calculate a Bézier-based ellipse that fits inside the quadrilateral qgon, an array with at least four Points that form a convex polygon, and add it to the current path.

It 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.)

Examples

ellipseinquad(box(O, 130, 130); action = :stroke)

ellipseinquad(box(O, 140, 230), :stroke)

References

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

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

Make a epitrochoid with short line segments, and add it to the current path. (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).

source
Luxor.fillpathMethod
fillpath()

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

source
Luxor.fillpreserveMethod
fillpreserve()

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

source
Luxor.fillstrokeMethod
fillstroke()

Fill and stroke the current path. After this, the current path is empty, and there is no current point.

source
Luxor.findbeziercontrolpointsMethod
findbeziercontrolpoints(
    previouspt::Point,
    pt1::Point,
    pt2::Point,
    nextpt::Point;
        smoothing = 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.

source
Luxor.finishMethod
finish(;svgpostprocess = false, addmarker = true)

Finish the drawing, close any related files. You may be able to view the drawing in another application with preview().

For more information about svgpostprocess and addmarker see help for Luxor._adjust_background_rects

source
Luxor.fontsizeMethod
fontsize(n)

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

source
Luxor.get_bezier_lengthMethod
get_bezier_length(bps::BezierPathSegment;
    steps=10)

Return the length of a BezierPathSegment, using steps to determine the accuracy, by stepping through the curve and finding all the points, and then measuring between them.

This is obviously just an approximation; the maths to do it properly is too difficult for me. :(

source
Luxor.get_bezier_pointsMethod
get_bezier_points(bps::BezierPathSegment;
    steps=10)

The flattening: return a list of all the points on the Bezier curve, including start and end, using steps to determine the accuracy.

source
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.

source
Luxor.getcolorMethod

getcolor()

Return an RGBA colorant, the current color, as set by setcolor etc...

source
Luxor.getlineMethod
getline()

Get the current line width, in points.

Use setline() to set the value.

source
Luxor.getmatrixMethod
getmatrix()

Get the current workspace (position, scale, and orientation) as a 6-element vector:

[xx, yx, xy, yy, x0, y0]
  • 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

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])

To modify the current 'matrix' by multiplying it by a 6 element 'matrix' a, see transform(a::Array).

To convert between Luxor/Cairo 'matrix' format (6-element Vector{Float64}) and a 3x3 Julia matrix, use cairotojuliamatrix(c) and juliatocairomatrix(c).

See also rotationmatrix(a), translationmatrix(), and scalingmatrix().

Extended help

Here are 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

source
Luxor.getmodeMethod
getmode()

Return the current compositing/blending mode as a string.

source
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?

See perpendicular().

source
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 like this:

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
source
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.

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

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

getrotation()
0.0

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

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

See getmatrix() for details.

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

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

Returns a tuple of x and y values.

getscale()
(1.0, 1.0)

See getmatrix() for details.

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

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

Returns a tuple of x and y values.

See getmatrix() for details.

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

Return the world coordinates of pt.

The default coordinate system for Luxor drawings is that the top left corner is 0/0. If you use origin() (or the various @- macro shortcuts), 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)
source
Luxor.grestoreMethod
grestore()

Replace the current graphics state with the one previously saved by the most recent gsave().

source
Luxor.gsaveMethod
gsave()

Save the current graphics environment, including current color settings.

source
Luxor.hascurrentpointMethod
hascurrentpoint()

Return true if there is a current point. This is the most recent point in the current path, as defined by one of the path building functions such as move(), line(), curve(), arc(), rline(), and rmove().

To obtain the current point, use currentpoint().

There's no current point after strokepath() and strokepath() calls.

source
Luxor.hexagons_withinMethod
hexagons_within(n::Int, hex::Hexagon)

Return all the hexagons within index distance n of hex. If n is 0, only the hex itself is returned. If n is 1, hex and the six hexagons one index away are returned. If n is 2, 19 hexagons surrounding hex are returned.

source
Luxor.hexcenterMethod
hexcenter(hex::Hexagon)

Find the center of the hex hexagon. Returns a Point.

source
Luxor.hexcube_linedrawMethod
hexcube_linedraw(hexa::Hexagon, hexb::Hexagon)

Find and return the hexagons that lie (mostly) on a straight line between hexa and hexb. If you filled/stroked them appropriately, you'd get a jagged line.

source
Luxor.hexcube_roundFunction
hexcube_round(x, y, origin, width = 10.0, height = 10.0)

Return the hexagon containing the point x, y, on the hexagonal grid centered at origin, and with tiles of width/height

point in Cartesian space can be mapped to the index of the hexagon that contains it.

source
Luxor.hexnearest_cubicMethod
 hexnearest_cubic(x::Real, y::Real, z::Real, origin, width, height)

Find the nearest hexagon in cubic coordinates, ie as q, r, s integer indices, given (x, y, z) as Real numbers, with the hexagonal grid centered at origin, and with tiles of width/height.

source
Luxor.hexneighborsMethod
hexneighbors(hex::Hexagon)

Return the neighbors of hex.

Example

julia> h = HexagonOffsetEvenR(0, 0, 70.0)

julia> hexneighbors(h)
HexagonNeighborIterator(HexagonCubic(0, 0, 0, Point(0.0, 0.0), 70.0, 70.0))

julia> collect(hexneighbors(h))
6-element Vector{Any}:
HexagonCubic(1, -1, 0, Point(0.0, 0.0), 70.0, 70.0)
HexagonCubic(1, 0, -1, Point(0.0, 0.0), 70.0, 70.0)
HexagonCubic(0, 1, -1, Point(0.0, 0.0), 70.0, 70.0)
HexagonCubic(-1, 1, 0, Point(0.0, 0.0), 70.0, 70.0)
HexagonCubic(-1, 0, 1, Point(0.0, 0.0), 70.0, 70.0)
HexagonCubic(0, -1, 1, Point(0.0, 0.0), 70.0, 70.0)
source
Luxor.hexringMethod
hexring(n::Int, hex::Hexagon)

Return the ring of hexagons that surround hex. If n is 1, the hexagons immediately surrounding hex are returned.

source
Luxor.hexspiralMethod
hexspiral(hex, n)

Return an array of hexagons to spiral around a central hexagon forming n rings.

source
Luxor.hextileMethod
 hextile(hex::Hexagon)

Calculate the six vertices of the hexagon hex and return them in an array of Points.

source
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.

source
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)])
source
Luxor.hypotrochoidMethod
hypotrochoid(R, r, d;
    action=:none,
    stepby=0.01,
    period=0.0,
    vertices=false)
hypotrochoid(R, r, d, action;
    stepby=0.01,
    period=0.0,
    vertices=false)

Make a hypotrochoid with short line segments, and add it to the current path. (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).

source
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)
source
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()
source
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)
source
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.

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

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

source
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.

source
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.

source
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.

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

Find the point where two lines intersect.

Return (resultflag, resultip), where resultflag is a Boolean, and resultip is a Point.

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.

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

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

source
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.

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

Returns true if pt is inside bounding box bb.

source
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.

source
Luxor.ispointonleftoflineMethod
ispointonleftofline(A::Point, B::Point, C::Point)

For a line passing through points A and B:

  • return true if point C is on the left of the line

  • return false if point C lies on the line

  • return false if point C is on the right of the line

source
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.

source
Luxor.ispolyclockwiseMethod
ispolyclockwise(pgon)

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

TODO This code is still experimental...

source
Luxor.ispolyconvexMethod
ispolyconvex(pts)

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

source
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.

source
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()

TODO Return something more useful than a Boolean.

source
Luxor.juliatocairomatrixMethod
juliatocairomatrix(c)

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

See getmatrix() for details.

source
Luxor.labelFunction
label(txt::T where T <: 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.

source
Luxor.labelFunction
label(txt::T where T <: AbstractString, direction::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
source
Luxor.latexboundingboxFunction
latexboundingbox(lstr::LaTeXString, font_size=get_fontsize(); halign=:left, valign=:right)

Returns the bounding box containing the latex text with (Lower Left Point, Upper Right Point). Use box(latex_bb(testext)...,:stroke) to draw the bounding box.

source
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).

source
Luxor.lineMethod
line(pt::Point)

Add a straight line to the current path that joins the path's current point to pt. The current point is then updated to pt.

Other path-building functions are move(), curve(), arc(), rline(), and rmove().

See also currentpoint() and hascurrentpoint().

source
Luxor.lineMethod
line(pt1::Point, pt2::Point; action=:path)
line(pt1::Point, pt2::Point, action=:path)

Add a straight line between two points pt1 and pt2, and do the action.

source
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.

source
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.

source
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.

source
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
source
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
source
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...

source
Luxor.midpointMethod
midpoint(a)

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

source
Luxor.moveMethod
move(pt)

Begin a new subpath in the current path, and set the current path's current point to pt, without drawing anything.

Other path-building functions are line(), curve(), arc(), rline(), and rmove().

hascurrentpoint() returns true if there is a current point.

source
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()).

source
Luxor.newpathMethod
newpath()

Discard the current path's contents. After this, the current path is empty, and there's no current point.

source
Luxor.newsubpathMethod
newsubpath()

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

source
Luxor.nextgridpointMethod
nextgridpoint(g::GridRect)

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

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

Draw a regular polygon centered at point Point(x,y).

source
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
source
Luxor.ngonsideFunction
ngonside(centerpoint::Point, sidelength::Real, sides::Int=5, orientation=0;
        action=:none,
        vertices=false,
        reversepath=false)
ngonside(centerpoint::Point, sidelength::Real, sides::Int, orientation, action;
        vertices=false,
        reversepath=false)

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

source
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.

source
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.

Used by offsetpoly().

source
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.

There must be at least 4 points in the polyline.

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)

TODO - rewrite it!

source
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.

source
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

source
Luxor.originMethod
origin(pt:Point)

Reset the current position, scale, and orientation, then move the 0/0 position to pt.

source
Luxor.originMethod
origin()

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

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

source
Luxor.paintMethod
paint()

Paint the current clip region with the current settings.

source
Luxor.pathlengthMethod
pathlength(path::Path;
    steps=10)

Return the length of a Path.

The steps parameter is used when approximating the length of any curve (Bezier) sections.

source
Luxor.pathsampleMethod
pathsample(path::Path, spacing;
    steps=10)

Return a new Path that resamples the path such that each line and curve of the original path is divided into sections that are approximately spacing units long.

The steps parameter is used when approximating the length of any curve (Bezier) sections. For measurement purposes, each Bezier curve is divided in steps straight lines; the error will be smaller for flatter curves and larger for more curvy ones.

source
Luxor.pathtobezierpathsMethod
pathtobezierpaths(
    ; flat=true)

Convert the current Cairo path (which may consist of one or more paths) to an array of Bézier paths, each of which is an array of BezierPathSegments. Each path segment is a tuple of four points. A straight line is converted to a Bézier 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 BezierPathSegments 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
source
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.

source
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?

source
Luxor.perpendicularMethod
perpendicular(p1::Point, p2::Point, p3::Point)

Return a point on a line passing through p1 and p2 that is perpendicular to p3.

source
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.

source
Luxor.pieMethod
pie(x, y, radius, startangle, endangle; action=:none)
pie(centerpoint, radius, startangle, endangle; action=:none)

Make a pie shape centered at x/y. Angles start at the positive x-axis and are measured clockwise, and add it to the current path.

See also sector().

TODO - return something more useful than a Boolean

source
Luxor.pieMethod
pie(centerpoint::Point, radius::Real, startangle::Real, endangle::Real, action::Symbol)
source
Luxor.pieMethod
pie(radius, startangle, endangle;
    action=:none)

Make a pie shape centered at the origin, and add it to the current path.

source
Luxor.placeepsMethod
placeeps(epsfile; log=false)

This function loads and interprets an EPS file previously exported by Cairo. Commands are 'applied' to the current drawing.

The primary intention is to extract some geometry - points and curves, etc. - from an EPS vector graphic.

Warning

This function reads only 'Cairo-flavoured' EPS files. Every application that exports EPS files defines its own chosen set of PostScript functions in a prolog. There's no standard. The 'Cairo-flavoured' EPS prolog is hard-coded into EPS files exported from Cairo so it's predictable, but other EPS exporters define their own flavour, defining and calling who knows what PostScript functions. So it's unlikely that EPS files from other sources will be successfully processed with this function.

Also note:

  • ignores clipping for now; I'm not sure how the Cairo->EPS->Cairo transformations work yet

  • ignores rectfill` commands for now - I think these are often used just for the initial BoundingBoxes/clipping, but if they're used matrix transforms they'll need interpreting somehow

  • ignores blends and gradients, embedded images, embedded fonts, among many other things...

Use log to print the commands to the REPL as well as execute them.

Examples

@draw begin
    translate(boxtopleft())
    Luxor.placeeps("/tmp/julia.eps")
end

You can convert an SVG file to EPS by reading the SVG into Luxor, saving as EPS, then placing that saved EPS file onto a new drawing:

svgfile = readsvg("julia.svg")
@eps begin
    placeimage(svgfile, centered = true)
end 800 500 "/tmp/julia.eps"
@draw begin
    translate(boxtopleft())
    placeeps("/tmp/julia.eps")
end

If you want to put the list of Luxor commands in a text file for pasting into another file, you could try this (Julia v.1.7 and up). Let's say you have an SVG called julia.svg.

# load an SVG and save as EPS
@eps begin
    svgf = readsvg("julia.svg")
    placeimage(svgf, centered = true)
end 500 500 "/tmp/t.eps"

# convert EPS to Luxor commands
redirect_stdio(stdout = "/tmp/output.jl") do
    placeeps("/tmp/t.eps", log = true)
end

# include Luxor commands and save as a new SVG:
@svg begin
    translate(boxtopleft())
    include("/tmp/output.jl")
end 500 500 "/tmp/julia.svg"
source
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.

source
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.

source
Luxor.placeimageMethod
placeimage(buffer::AbstractMatrix{ARGB32}, args...; 
    kargs...)

Place an array of ARGB32 lements on the drawing at pos with opacity/transparency alpha. Values are "alpha-premultiplied" before being placed.

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

source
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.

source
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.

source
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).

source
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.

source
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).

source
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.

source
Luxor.polarMethod
polar(r, theta)

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

polar(10, pi/4)

produces

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

Make a polygon around the BoundingBox in bbox.

source
Luxor.polyMethod

Draw a polygon.

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

Create a path with the points in pointlist and apply action. By default poly() doesn't close or fill the polygon.

source
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().

source
Luxor.polycentroidMethod

Find the centroid of a simple polygon.

polycentroid(pointlist)

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

You could test the point using isinside().

source
Luxor.polyclipMethod
polyclip(s, c)

Return a polygon that defines the intersection between an subject polygon and the clip polygon.

Return nothing if the function can't find one.

S - subject polygon - can be concave or convex.

C - clip polygon - must be convex.

Uses the Sutherland-Hodgman clipping algorithm. Calls ispointonleftofline().

source
Luxor.polycrossFunction
polycross(pt::Point, radius, npoints::Int, ratio=0.5, orientation=0.0;
    action      = :none,
    splay       = 0.5,
    vertices    = false,
    reversepath = false)
polycross(pt::Point, radius, npoints::Int, ratio=0.5, orientation=0.0, action;
    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 executing the action.

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

Examples

polycross(O, 100, 5,
    action = :fill,
    splay  = 0.5)

polycross(O, 120, 5, 0.5, 0.0, :stroke,
    splay  = 0.5)
source
Luxor.polydistancesMethod
polydistances(p::Array{Point, 1}; closed=true)

Return an array of the cumulative lengths of a polygon.

source
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.

source
Luxor.polyhullMethod
polyhull(pts)

Find all points in pts that form a convex hull around the points in pts, and return them.

This uses the Graham Scan algorithm.

TODO : experimental, can be improved.

source
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.

source
Luxor.polymorphMethod
polymorph(pgon1::Array{Array{Point,1}}, pgon2::Array{Array{Point,1}}, k;
    samples = 100,
    easingfunction = easingflat,
    kludge = true
    closed = true)

"morph" is to gradually change from one thing to another. This function changes one polygon into another.

It returns an array of polygons, [p_1, p_2, p_3, ... ], where each polygon p_n is the intermediate shape between the corresponding shape in pgon1[1...n] and pgon2[1...n] at k, where 0.0 < k < 1.0. If k ≈ 0.0, the pgon1[1...n] is returned, and if `k ≈ 1.0, pgon2[1...n] is returned.

pgon1 and pgon2 can be either simple polygons or arrays of one or more polygonal shapes (eg as created by pathtopoly()). For example, pgon1 might consist of two polygonal shapes, a square and a triangular shaped hole inside; pgon2 might be a triangular shape with a square hole.

It makes sense for both arguments to have the same number of polygonal shapes. If one has more than another, some shapes would be lost when it morphs. But the suggestively-named kludge keyword argument, when set to (the default) true, tries to compensate for this.

By default, easingfunction = easingflat, so the intermediate steps are linear. If you use another easing function, intermediate steps are determined by the value of the easing function at k.

Because polysample() can treat the polygon as open or closed (with different results), you can specify how the sampling is done here, with the closed= keyword:

  • closed = true -> polygons are sampled as closed

  • closed = false -> polygons are sampled as open

  • closed = (true, false) -> first polygon is sampled as closed, second as open

This function isn't very efficient, because it copies the polygons and resamples them.

TODO : experimental, can surely be improved.

Extended help

Examples

This simple morph between a small square and a larger octagon is controlled by the easing function easeinoutinversequad, which slows down around the middle of the transition.

Only the first shape of the returned polygon array is needed.

pgon1 = ngon(O, 30, 4, 0, vertices = true)
pgon2 = ngon(O, 220, 8, 0, vertices = true)
for i in 0:0.1:1.0
    poly(first(polymorph(pgon1, pgon2, i,
            easingfunction = easeinoutinversequad)),
        action = :stroke,
        close = true)
end

This next example morphs between the first shape - a circle with a square hole - and the second shape, a square with a circular hole.

ngon(O - (250, 0), 30, 50, 0, :path)
newsubpath()
ngon(O - (250, 0), 10, 4, 0, reversepath = true, :path)
pg1 = pathtopoly()

newpath()
ngon(O + (250, 0), 30, 4, 0, :path)
newsubpath()
ngon(O + (250, 0), 10, 50, 0, reversepath = true, :path)
pg2 = pathtopoly()

for i in reverse(0.0:0.1:1.0)
    randomhue()
    newpath()
    # use :path followed by fillpath() to preserve correct "hole"-iness
    poly.(polymorph(pg1, pg2, i), :path, close = true)
    fillpath()
end
source
Luxor.polymove!Method
polymove!(pgon, frompoint::Point, topoint::Point)

Move (permanently) a polygon from frompoint to topoint.

source
Luxor.polyorientationMethod
polyorientation(pgon)

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

TODO This code is still experimental...

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

Find the total length of the sides of polygon p.

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

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.

source
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.

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

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.

source
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...

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

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

source
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.

If include_first is true, the first point of plist is included in the result.

If the resulting polygon's first and end points are the same, the end point is discarded.

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

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

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

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

source
Luxor.polysmoothMethod
polysmooth(points, radius, action=:action; debug=false)
polysmooth(points, radius; action=:none, 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.

TODO Return something more useful than a Boolean.

source
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 default minimum point is usually OK too:

polysortbyangle(parray, polycentroid(parray))
source
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.

source
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.

source
Luxor.polysuperFunction
polysuper(center::Point = Point(0, 0);
    n1 = 1,
    n2 = 1,
    n3 = 1,
    m = 2,
    a = 1,
    b = 1,
    radius = 100,
    action = :none,
    vertices = false,
    reversepath = false,
    stepby = π / 120)

Build a supershape, a generalization of the superellipse, and apply action.

Based upon equations by Johan Gielis.

See also: squircle()...

Example

polysuper(m=4, 
    n1=13, n2=11, n3=3,
    a=3, b=4,
    action=:fill, 
    radius=200)

produces a weird eye/lemon shape, a bit like this:


                        @@@@@@S                       
                  @@@@@@@@@@@@@@@@@@                  
               @@@@@@@@@@@@@@@@@@@@@@@@               
            R@@@@@@@@@@@@@@@@@@@@@@@@@@@@@            
          @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@         
      @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@      
   @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@   
     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@     
         @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@         
            @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@            
              E@@@@@@@@@@@@@@@@@@@@@@@@@              
                  @@@@@@@@@@@@@@@@@@o                 
                      @@@@@@@@@@                      
source
Luxor.polytopathMethod
polytopath(ptlist)

Convert a polygon to a Path object.

@draw drawpath(polytopath(ngon(O, 145, 5, vertices = true)), action = :fill)
source
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.

source
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.

source
Luxor.prettypolyFunction
prettypoly(points::Array{Point, 1}, vertexfunction = () -> circle(O, 2, :stroke);
    action=:none,
    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, action=: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, action=:stroke,
    vertexlabels = (n, l) -> (text(string(n, " of ", l))))
source
Luxor.previewMethod
preview()

If you're working in a notebook (eg Jupyter/IJulia), display a PNG or SVG file in the notebook.

If you're working in VS-Code, display a PNG or SVG file in the Plots 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.
source
Luxor.randompointMethod
randompoint(lowx, lowy, highx, highy)

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

source
Luxor.randompointMethod
randompoint(lowpt, highpt)

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

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

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

source
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
source
Luxor.randompointarrayMethod
randompointarray(bbox::BoundingBox, d; attempts=20)

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

source
Luxor.randompointarrayMethod
randompointarray(lowpt, highpt, n)

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

source
Luxor.rawlatexboundingboxMethod
rawlatexboundingbox(lstr::LaTeXString, font_size=1)

Helper function that returns the coordinate points of the bounding box containing the specific LaTeX text.

source
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
source
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
source
Luxor.rectMethod
rect(xmin, ymin, w, h; action=:none)
rect(xmin, ymin, w, h, action)

Create a rectangle with one corner at (xmin/ymin) with width w and height h, and add it to the current path. Then apply action.

Returns a tuple of two points, the corners of a bounding box that encloses the rectangle.

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

source
Luxor.rectMethod
rect(cornerpoint, w, h; action = none, reversepath=false,
    vertices=false)
rect(cornerpoint, w, h, action; reversepath=false,
    vertices=false)

Create a rectangle with one corner at cornerpoint with width w and height h, and add it to the current path. Then apply 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.

source
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
source
Luxor.rlineMethod
rline(pt)

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

source
Luxor.rmoveMethod
rmove(pt)

Begin a new subpath in the current path, add pt to the current path's current point, then update the current point.

Other path-building functions are move(), line(), curve(), arc(), and rline().

There must be a current point before you call this function.

See also currentpoint() and hascurrentpoint().

source
Luxor.rotateMethod
rotate(a::Float64)

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

Values are relative to the current orientation.

source
Luxor.rotatepointMethod
rotatepoint(targetpt::Point, angle)

Rotate a target point around the current origin by an angle specified in radians.

Returns the new point.

source
Luxor.rotatepointMethod
rotatepoint(targetpt::Point, originpt::Point, angle)

Rotate a target point around another point by an angle specified in radians.

Returns the new point.

source
Luxor.rotationmatrixMethod
rotationmatrix(a)

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

See getmatrix() for details.

source
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.

source
Luxor.rulersMethod
rulers()

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

source
Luxor.scaleMethod
scale(x, y)

Scale the current workspace by different values in x and y.

Values are relative to the current scale. Example:

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

Scale the current workspace by f in both x and y directions.

Values are relative to the current scale.

source
Luxor.scalingmatrixMethod
scalingmatrix(sx, sy)

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

See getmatrix() for details.

source
Luxor.sectorMethod
sector(innerradius::Real, outerradius::Real, startangle::Real, endangle::Real;
   action=:none)

Make an annular sector centered at the origin, and add it to the current path.

source
Luxor.sectorMethod
sector(centerpoint::Point, innerradius, outerradius, startangle, endangle;
    action=:none)

Make an annular sector centered at centerpoint, and add it to the current path.

TODO - return something more useful than a Boolean

See also: pie().

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

Make an annular sector with rounded corners, basically a bent sausage shape, centered at centerpoint, and add it to the current path.

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

TODO - return something more useful than a Boolean

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

source
Luxor.sectorMethod
sector(innerradius::Real, outerradius::Real, startangle::Real, endangle::Real,
   cornerradius::Real, action)

Make an annular sector with rounded corners, centered at the current origin, and add it to the current path.

source
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.

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

Return a new BezierPathSegment with new locations for the Bezier control points in the BezierPathSegment 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.

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

Return a new BezierPath with new locations for the Bézier control points in every Bézier 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.

source
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.

source
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

source
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.

source
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.

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

Set the dash pattern for lines to the values in dashes. The first number is the length of the inked portion, the second the space, 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.

source
Luxor.setdashMethod
setdash("dot")

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

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

source
Luxor.setfillruleFunction
setfillrule(rule::Symbol)

Set the fill rule for paths for the current drawing.

rule can be :winding or :even_odd

The fill rule is used to select how subpaths are filled. For both fill rules, whether or not a point is included in the fill is determined by taking a ray from that point to infinity and looking at intersections with the path. The ray can be in any direction, as long as it doesn't pass through the end point of a segment or have a tricky intersection such as intersecting tangent to the path. (Note that filling is not actually implemented in this way. This is just a description of the rule that is applied.)

The default fill rule is :winding.

:winding: If the path crosses the ray from left-to-right, counts +1. If the path crosses the ray from right to left, counts -1. (Left and right are determined from the perspective of looking along the ray from the starting point.) If the total count is non-zero, the point will be filled.

even_odd: counts the total number of intersections, without regard to the orientation of the contour. If the total number of intersections is odd, the point will be filled. (Since 1.0)

See getfillrule.

source
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))
source
Luxor.setgrayMethod
setgray(n)
setgrey(n)

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

source
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.

source
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.

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

Set the color without changing the current alpha/opacity:

source
Luxor.setlineMethod
setline(n)

Set the line width, in points.

Use getline() to get the current value.

source
Luxor.setlinecapFunction
setlinecap(s)

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

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

Set the way line segments are joined when the path is stroked.

The default joining style is "mitered".

source
Luxor.setmatrixMethod
setmatrix(m::Array)

Change the current matrix to 6-element matrix m.

See getmatrix() for details.

source
Luxor.setmeshMethod
setmesh(mesh::Mesh)

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

source
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 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.

source
Luxor.setopacityMethod
setopacity(alpha)

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

source
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>
source
Luxor.shiftbezierhandlesMethod
shiftbezierhandles(bps::BezierPathSegment;
    angles=[0.1, -0.1],
    handles=[1.1, 1.1])

Return a new BezierPathSegment that modifies the Bézier 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.

source
Luxor.simplifyFunction

Simplify a polygon:

simplify(pointlist::Array, detail=0.1)

detail is the maximum approximation error of simplified polygon.

source
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.

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

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.

addmarker for more information about addmarker see help for Luxor._adjust_background_rects

?Luxor._adjust_background_rects

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.

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

Make a spiral, and add it to the current path. 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.

source
Luxor.splitbezierMethod
splitbezier(bps::BezierPathSegment, t)

Split the Bezier path segment at t, where t is between 0 and 1.

Use Paul de Casteljaus' algorithm (the man who really introduced Bezier curves...).

Returns a tuple of two BezierPathSegments, the 'lower' one (0 to t) followed by the 'higher' one (t to 1).

Example

julia> l
bps = BezierPathSegment(ngon(O, 200, 4, vertices=true)...)
l, h = splitbezier(bps::BezierPathSegment, 0.5)

julia> h
4-element BezierPathSegment:
 Point(1.2246467991473532e-14, 200.0)
 Point(-100.0, 100.00000000000001)
 Point(-100.0, 1.4210854715202004e-14)
 Point(-50.00000000000001, -49.99999999999999)

julia> l.p2 == h.p1 true

source
Luxor.splittextMethod
splittext(s)

Split the text in string s into an array, but keep all the separators attached to the preceding word.

source
Luxor.squircleMethod
squircle(center::Point, hradius, vradius;
    action=:none,
    rt = 0.5, stepby = pi/40, vertices=false)
squircle(center::Point, hradius, vradius, action;
    rt = 0.5, stepby = pi/40, vertices=false)

Make a squircle or superellipse (basically a rectangle with rounded corners), and add it to the current path. 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.

See also: polysuper().

source
Luxor.starFunction
star(center, radius, npoints, ratio=0.5, orientation, action=:none;
    vertices = false, reversepath=false)
star(center, radius, npoints, ratio=0.5, orientation=0.0;
    action=:none, vertices = false, reversepath=false)

Make a star centered at center with npoints sections oriented by orientation. 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 making it.

Examples

star(O, 120, 5, 0.5, 0.0, :fill,
    vertices = false,
    reversepath=false)

star(O, 220, 5, 0.5;
    action=:stroke,
    vertices = false,
    reversepath=false)
source
Luxor.storepathMethod
storepath()

Obtain the current Cairo path and make a Luxor Path object, which is an array of PathElements.

Returns the Path object.

You can draw stored paths using drawpath().

See also getpath(), getpathflat(), and textpath().

source
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 emptied.

source
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.

source
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 examines the generated SVG code produced by drawing 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%);")

Here's another example, post-processing the SVG file with the svgo optimizer.

@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`)
source
Luxor.texalignMethod
texalign(halign, valign, bottom_pt, top_pt, font_size)

Helper function to align LaTeX text properly. Returns translate_x and translate_y which consists of the amount to be shifted depending on the type of alignment chosen and the bounding box of the text.

source
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)
text(latexstr, pos, valign = :baseline, halign = :left, rotationfixed = false, angle = 0)

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 function uses Cairo's Toy text API.

Other text functions:

textextents() - find the dimensions of some text in the current font

settext() - like text() but using the Pro Api

setfont() - like fontface() but using the Pro Api

label() - draw text relative to a point with offset and leader lines

textbox() - draw an array of strings vertically downwards

textcurve() - draw text on a circular arc

textfit() - fit text into a BoundingBox by resizing it

textlines() - splits text into an array of strings so as to fit width

textonpoly() - draw text that sits on the edges of a polygon

textpath() - convert text to paths (straight lines and Bezier segments)

textoutlines() - Convert text to polygons (straight lines of varying lengths)

textplace() - draw text character by character with font/size/position specs

texttrack() - draw text tracked (tight or loose)

textwrap() - draw text to fit a box after re-justifying the lines to fit nicely

source
Luxor.textMethod
text(lstr::LaTeXString, pt::Point;
    valign=:baseline,
    halign=:left,
    rotationfixed = false,
    angle=0,
    paths=false,
    kwargs...)

Draws LaTeX string using MathTexEngine.jl. Hence, uses ModernCMU as font family. When rotationfixed = true, the text will rotate around its own axis, instead of rotating around pt.

If paths is true, text paths are added to the current path, rather than drawn.

using Luxor
using MathTeXEngine
using LaTeXStrings
@draw begin
    fontsize(70)
    text(L"e^{i\pi} + 1 = 0", halign=:center)
end
source
Luxor.textboxFunction
textbox(s::T where T <: AbstractString, pos::Point=O;
    leading = 12,
    linefunc::Function = (linenumber, linetext, startpos, height) -> (),
    alignment=:left)
source
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.

source
Luxor.textcurveFunction
textcurve(the_text, start_angle, start_radius, pos;
      # 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 pos with radius start_radius.

source
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.

source
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]
source
Luxor.textfitFunction
textfit(str, bbox::BoundingBox, maxfontsize = 800;
     horizontalmargin=12,
     leading=100)

Fit the string str into the bounding box bbox by adjusting the font size and line breaks.

Instead of using the current font size, the largest possible value will be calculated. You can specify a size limit in maxfontsize, such that the text will never be larger than this value, although it may have to be smaller.

horizontalmargin is applied to each side.

Optionally, leading can be supplied, and this will be interpreted as a percentage of the final calculated font size. The default value is 110 (%).

The function returns a named tuple with information about the calculated values:

(fontsize = 37.6, linecount = 5, finalpos = Point(-117.43, 92.60)
Note

This function is in need of improvement. It's quite difficult to find out the height of a line of text in a specific font. (Unless we import FreeType.) Suggestions for improvements welcome!

source
Luxor.textlinesMethod
textlines(s::T where T <: 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?

source
Luxor.textonpolyMethod
textonpoly(str, pgon;
        tracking = 0,
        startoffset = 0.0,
        baselineshift = 0.0,
        closed = false)

Draw the text in str along the route of the polygon in pgon.

The closed option determines whether the final edge of the polygon (joining the last point to the first) is included or not. Eg if you want to draw a string around all three sides of a triangle, you'd use closed=true:

textonpoly("mèdeis ageômetrètos eisitô mou tèn
stegèn - let no one ignorant of geometry come under my roof
",
    ngon(O, 100, 3, vertices=true),
    closed=true)

If false, only two sides are considered.

Increase tracking from 0 to add space between the glyphs.

The startoffset value is a normalized percentage that specifies the start position. So, to start drawing the text halfway along the polygon, specify a start offset value of 0.5.

Positive values for baselineshift move the characters upwards from the baseline.

Returns a tuple with the number of characters drawn, and the final value of the index, between 0.0 and 1.0. If the returned index value is less than 1, this means that the text supplied ran out before the end of the polygon was reached.

source
Luxor.textoutlinesMethod
textoutlines(s::String, pos::Point=O;
    action=:none,
    halign=:left,
    valign=:baseline,
    startnewpath=true)

Convert text to polygons and apply action.

By default this function discards any current path, unless you use startnewpath=false

See also textpath(). textpath() retains Bezier curves, whereas textoutlines() returns flattened curves.

TODO Return something more useful than a Boolean.

source
Luxor.textpathMethod
textpath(s::String, pos::Point;
    action=:none,
    halign=:left,
    valign=:baseline,
    startnewpath=true)

Convert the text in string s to paths and apply the action.

TODO Return something more useful than a Boolean.

source
Luxor.textpathMethod
textpath(t)

Convert the text in string t to paths, adding them to the current path, for subsequent filling/stroking etc...

You can use pathtopoly() or getpath() or getpathflat() to convert the paths to polygons.

See also textoutlines(). textpath() retains Bezier curves, whereas textoutlines() returns flattened curves.

source
Luxor.textplaceMethod
textplace(txt::T where T <: AbstractString, pos::Point, params::Vector;
    action = :fill,
    startnewpath = false)

A low-level function that places text characters one by one according to the parameters in params. First character uses the first tuple, second character uses the second, and so on.

Returns the next text position.

A tuple of parameters is:

(face = "TimesRoman", size = 12, color=colorant"black", kern = 0, shift = 0, advance = true)

where

  • face is fontface "string" # sticky

  • size is fontsize # pts # sticky

  • color is color # sticky

  • kern amount (pixels) shifted to the right # resets after each char

  • shift = baseline shifted vertically # resets after each char

  • advance - whether to advance # resets after each char

Some parameters are "sticky": once set, they apply for all subsequent characters until a new value is supplied. Others aren't sticky, and are reset for each character. So font face, size, and color parameters need only be specified once, whereas kern/shift/advance modifiers are reset for each character.

Example

Draw the Hogwarts Express Platform number 9 and 3/4:

txtpos = textplace("93—4!", O - (200, 0), [
    # format for 9:
    (size=120, face="Bodoni-Poster", color=colorant"grey10"),
    # format for 3:
    (size=60,  kern = 5, shift = 60,  advance=false,),
    # format for -:
    (          kern = 0, shift = 25,  advance=false,),
    # format for 4:
    (          kern = 5, shift = -20, advance=true),
    # format for !:
    (size=120, kern = 20,),
    ])
source
Luxor.texttrackMethod
texttrack(txt, pos, tracking;
    action=:fill,
    halign=:left,
    valign=:baseline,
    startnewpath=true)
texttrack(txt, pos, tracking, fontsize;
    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. In a 12‑point font, 1 em equals 12 points. A point is about 0.35mm, 1em is about 4.2mm, and a 1000 units of tracking are about 4.2mm. So a tracking value of 1000 for a 12 point font places about 4mm between each character.

A negative value tightens the letter spacing noticeably.

The text drawing action applied to each character defaults to textoutlines(... :fill).

If startnewpath is true, each character is acted on separately. To clip and track text, specify the clip action and avoid resetting the clipping path for each character.

    newpath()
    texttrack(t, O + (0, 80), 200, action=:clip, startnewpath=false)
    ...
    clipreset()

TODO Is it possible to fix strings with combining characters such as "̈"?

source
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.

source
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)
source
Luxor.tidysvgMethod
tidysvg(fromfile, tofile)

Read the SVG image in fromfile and write it to tofile with modified glyph names.

source
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 browsers and notebooks. See this github issue for details.

A kludgy workround is to rename the elements.

source
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])

See getmatrix() for details.

source
Luxor.translateMethod
translate(point)
translate(x::Real, y::Real)

Translate the current workspace to x and y or to pt.

Values are relative to the current location.

source
Luxor.translationmatrixMethod
translationmatrix(x, y)

Return a 3x3 Julia matrix that will apply a translation in x and y.

See getmatrix() for details.

source
Luxor.trianglecenterMethod
trianglecenter(pt1::Point, pt2::Point, pt3::Point)

Return the centroid of the triangle defined by pt1, pt2, and pt3.

source
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.

source
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.

source
Luxor.triangleorthocenterMethod
triangleorthocenter(pt1::Point, pt2::Point, pt3::Point)

Return the orthocenter of the triangle defined by pt1, pt2, and pt3.

source
Luxor.trimbezierMethod
trimbezier(bps::BezierPathSegment, lowpt, highpt)

Chop the ends of a BezierPathSegment at lowpt and highpt. lowpt and highpt should be between 0 and 1.

Returns a trimmed BezierPathSegment.

source
Luxor.writelatexcharMethod
writelatexchar(t::AbstractString)

Helper function to handle extra chars that are not supported in MathTeXEngine.

source
Luxor.BezierPathType

BezierPath is an array of BezierPathSegments. segments is Vector{BezierPathSegment}.

source
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

source
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() and textextents()).

Text is assumed to be placed at the origin (0/0).

source
Luxor.BoundingBoxMethod
BoundingBox(tile::BoxmapTile)

Return a BoundingBox of a BoxmapTile (as created with boxmap()).

source
Luxor.BoundingBoxMethod
BoundingBox(path::Path)

Find bounding box of a stored Path (made with storepath()).

source
Luxor.BoundingBoxMethod
BoundingBox(t::table, rownumber, columnnumber)

Return a BoundingBox that encloses cell at row rownumber, column colnumber of table t.

source
Luxor.BoundingBoxMethod
BoundingBox(t::table, cell)

Return a BoundingBox that encloses cell cell of table t.

source
Luxor.BoundingBoxMethod
BoundingBox(t::Tiler, r, c)

Return the Bounding Box enclosing the tile at row r column c.

source
Luxor.BoundingBoxMethod
BoundingBox(pointlist::Array)

Return the BoundingBox of a polygon (array of points).

source
Luxor.BoundingBoxMethod

BoundingBox() with no arguments returns a BoundingBox that includes the current drawing.

The default BoundingBox(;centered=true) returns a BoundingBox the same size and position as the current drawing, and assumes the origin (0, 0) is at the center.

If the centered option is 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.)

An instance of 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 at the origin

BoundingBox(pt::Array) # the bounding box of a polygon

BoundingBox(circle(O, 100)) # the bounding box of a path added by circle()

BoundingBox(path::Path) # the bounding box of a Path

You can use BoundingBox() with the functions that add graphic shapes to the current path (eg box(), circle(), star(), ngon()). But note that eg BoundingBox(box(O, 100, 100)) adds a shape to the current path as well as returning a bounding box.

source
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.)

Drawing(img, strokescale=true)

creates the drawing from an existing image buffer of type Matrix{Union{RGB24,ARGB32}}, e.g.:

using Luxor, Colors
buffer=zeros(ARGB32, 100, 100)
d=Drawing(buffer)
source
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.

source
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.

source
Luxor.HexagonType
Hexagon

To create a hexagon, use one of the types:

  • HexagonOffsetOddR q r origin w h
  • HexagonOffsetEvenR q r origin w h
  • HexagonAxial q r origin w h
  • HexagonCubic q r s origin w h

Functions:

  • hextile(hex::Hexagon) - calculate the six vertices
  • hexcenter(hex::Hexagon) - center
  • hexring(n::Int, hex::Hexagon) - array of hexagons surrounding hex
  • hexspiral(hex::Hexagon, n) - arry of hexagons in spiral
  • hexneighbors(hex::Hexagon) - array of neighbors of hexagon
source
Luxor.HexagonAxialType
HexagonAxial

Two axes

q:: first index

r:: second index

origin::Point

width:: of tile

height:: of tile

source
Luxor.HexagonCubicType
HexagonCubic

Three axes

q:: first index

r:: second index

s:: third index

origin::Point

width:: of tile

height:: of tile

source
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")
source
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.

source
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.

source
Luxor.PathType

A Path object contains, in the .path field, a vector of PathElements (PathCurve, PathMove, PathLine, PathClose) that describe a Cairo path. Use drawpath() to draw it.

Path([PathMove(Point(2.0, 90.5625)),
      PathCurve(Point(4.08203, 68.16015), Point(11.28, 45.28), Point(24.8828, 26.40234)),
      PathLine(Point(2.0, 90.5625)),
      PathClose()
     ])
source
Luxor.PathMethod
Path(ptlist::Vector{Point}; close=false))

Create a Path from the points in ptlist.

source
Luxor.PointType

The Point type holds two coordinates. It's immutable, you can't change the values of the x and y values directly.

source
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
source
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)
source
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.

source
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.

source
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().

source
Luxor.OConstant

O is a shortcut for the current origin, 0/0

source
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))
source