Luxor.@draw
— Macro@draw drawing-instructions [width] [height]
Preview an PNG drawing, optionally specifying width and height (the default is 600 by 600). The drawing is stored in memory, not in a file on disk.
Examples
@draw circle(O, 20, :fill)
@draw circle(O, 20, :fill) 400
@draw circle(O, 20, :fill) 400 1200
@draw begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end
@draw begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end 1200 1200
Luxor.@drawsvg
— Macro@drawsvg begin
body
end w h
Create and preview an SVG drawing. Like @draw
but using SVG format.
Unlike @draw
(PNG), there is no background, by default.
Luxor.@eps
— Macro@eps drawing-instructions [width] [height] [filename]
Create and preview an EPS drawing, optionally specifying width and height (the default is 600 by 600). The file is saved in the current working directory as filename
if supplied, or luxor-drawing(timestamp).eps
.
On some platforms, EPS files are converted automatically to PDF when previewed.
Examples
@eps circle(O, 20, :fill)
@eps circle(O, 20, :fill) 400
@eps circle(O, 20, :fill) 400 1200
@eps circle(O, 20, :fill) 400 1200 "/tmp/A0-version"
@eps circle(O, 20, :fill) 400 1200 "/tmp/A0-version.eps"
@eps begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end
@eps begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end 1200 1200
Luxor.@imagematrix
— Macro@imagematrix drawing-instructions [width=256] [height=256]
Create a drawing and return a matrix of the image.
This macro returns a matrix of pixels that represent the drawing produced by the vector graphics instructions. It uses the image_as_matrix()
function.
The default drawing is 256 by 256 points.
You don't need finish()
(the macro calls it), and it's not previewed by preview()
.
m = @imagematrix begin
sethue("red")
box(O, 20, 20, :fill)
end 60 60
julia> m[1220:1224] |> show
ARGB32[ARGB32(0.0N0f8,0.0N0f8,0.0N0f8,0.0N0f8),
ARGB32(1.0N0f8,0.0N0f8,0.0N0f8,1.0N0f8),
ARGB32(1.0N0f8,0.0N0f8,0.0N0f8,1.0N0f8),
ARGB32(1.0N0f8,0.0N0f8,0.0N0f8,1.0N0f8),
ARGB32(1.0N0f8,0.0N0f8,0.0N0f8,1.0N0f8)]
If, for some strange reason you want to draw the matrix as another Luxor drawing again, use code such as this:
m = @imagematrix begin
sethue("red")
box(O, 20, 20, :fill)
sethue("blue")
box(O, 10, 40, :fill)
end 60 60
function convertmatrixtocolors(m)
return convert.(Colors.RGBA, m)
end
function drawimagematrix(m)
d = Drawing(500, 500, "/tmp/temp.png")
origin()
w, h = size(m)
t = Tiler(500, 500, w, h)
mi = convertmatrixtocolors(m)
@show mi[30, 30]
for (pos, n) in t
c = mi[t.currentrow, t.currentcol]
setcolor(c)
box(pos, t.tilewidth -1, t.tileheight - 1, :fill)
end
finish()
return d
end
drawimagematrix(m)
Transparency
The default value for the cells in an image matrix is transparent black. (Luxor's default color is opaque black.)
julia> @imagematrix begin
end 2 2
2×2 reinterpret(ARGB32, ::Array{UInt32,2}):
ARGB32(0.0,0.0,0.0,0.0) ARGB32(0.0,0.0,0.0,0.0)
ARGB32(0.0,0.0,0.0,0.0) ARGB32(0.0,0.0,0.0,0.0)
Setting the background to a partially or completely transparent value may give unexpected results:
julia> @imagematrix begin
background(1, 0.5, 0.0, 0.5) # semi-transparent orange
end 2 2
2×2 reinterpret(ARGB32, ::Array{UInt32,2}):
ARGB32(0.502,0.251,0.0,0.502) ARGB32(0.502,0.251,0.0,0.502)
ARGB32(0.502,0.251,0.0,0.502) ARGB32(0.502,0.251,0.0,0.502)
here the semi-transparent orange color has been partially applied to the transparent background.
julia> @imagematrix begin
sethue(1., 0.5, 0.0)
paint()
end 2 2
2×2 reinterpret(ARGB32, ::Array{UInt32,2}):
ARGB32(1.0,0.502,0.0,1.0) ARGB32(1.0,0.502,0.0,1.0)
ARGB32(1.0,0.502,0.0,1.0) ARGB32(1.0,0.502,0.0,1.0)
picks up the default alpha of 1.0.
Luxor.@imagematrix!
— Macro@imagematrix! buffer drawing-instructions [width=256] [height=256]
Like @imagematrix
, but use an existing UInt32 buffer.
w = 200
h = 150
buffer = zeros(UInt32, w, h)
m = @imagematrix! buffer juliacircles(40) 200 150;
Images.RGB.(m)
Luxor.@layer
— MacroThe `layer` macro is a shortcut for `gsave()` ... `grestore()`.
Luxor.@pdf
— Macro@pdf drawing-instructions [width] [height] [filename]
Create and preview an PDF drawing, optionally specifying width and height (the default is 600 by 600). The file is saved in the current working directory as filename
if supplied, or luxor-drawing(timestamp).pdf
.
Examples
@pdf circle(O, 20, :fill)
@pdf circle(O, 20, :fill) 400
@pdf circle(O, 20, :fill) 400 1200
@pdf circle(O, 20, :fill) 400 1200 "/tmp/A0-version"
@pdf circle(O, 20, :fill) 400 1200 "/tmp/A0-version.pdf"
@pdf begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end
@pdf begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end 1200 1200
Luxor.@png
— Macro@png drawing-instructions [width] [height] [filename]
Create and preview an PNG drawing, optionally specifying width and height (the default is 600 by 600). The file is saved in the current working directory as filename
, if supplied, or luxor-drawing(timestamp).png
.
Examples
@png circle(O, 20, :fill)
@png circle(O, 20, :fill) 400
@png circle(O, 20, :fill) 400 1200
@png circle(O, 20, :fill) 400 1200 "/tmp/round"
@png circle(O, 20, :fill) 400 1200 "/tmp/round.png"
@png begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end
@png begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end 1200 1200
Luxor.@polar
— Macro@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)
Luxor.@savesvg
— Macro@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%);")
Luxor.@setcolor_str
— MacroSet the current color to a string using a macro.
For example:
setcolor"red"
Luxor.@svg
— Macro@svg drawing-instructions [width] [height] [filename]
Create and preview an SVG drawing, optionally specifying width and height (the default is 600 by 600). The file is saved in the current working directory as filename
if supplied, or luxor-drawing-(timestamp).svg
.
Examples
@svg circle(O, 20, :fill)
@svg circle(O, 20, :fill) 400
@svg circle(O, 20, :fill) 400 1200
@svg circle(O, 20, :fill) 400 1200 "/tmp/test"
@svg circle(O, 20, :fill) 400 1200 "/tmp/test.svg"
@svg begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end
@svg begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end 1200 1200
Base.:*
— 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()
.
Base.convert
— Methodconvert(Point, bbox::BoundingBox)
Convert a BoundingBox to a four-point clockwise polygon.
convert(Vector{Point}, BoundingBox())
Base.hcat
— Methodhcat(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)
Base.in
— Methodin(pt, bbox::BoundingBox)
Test whether pt
is inside bbox
.
Base.isapprox
— Methodisapprox(p1::Point, p2::Point; atol = 1e-6, kwargs...)
Compare points.
Base.isequal
— Methodisequal(p1::Point, p2::Point) = isapprox(p1.x, p2.x, atol = 0.00000001) && (isapprox(p1.y, p2.y, atol = 0.00000001))
Compare points.
Base.rand
— Methodrand(bbox::BoundingBox)
Return a random Point
that lies inside bbox
.
Base.vcat
— Methodvcat(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)
Luxor.Circle
— FunctionCircle(t::Turtle, radius=1.0)
Draw a filled circle centered at the current position with the given radius.
Luxor.Forward
— FunctionForward(t::Turtle, d=1)
Move the turtle forward by d
units. The stored position is updated.
Luxor.HueShift
— FunctionHueShift(t::Turtle, inc=1.0)
Shift the Hue of the turtle's pen forward by inc
. Hue values range between 0 and 360. (Don't start with black, otherwise the saturation and brightness values will be black.)
Luxor.Message
— MethodMessage(t::Turtle, txt)
Write some text at the current position.
Luxor.Orientation
— FunctionOrientation(t::Turtle, r=0.0)
Set the turtle's orientation to r
degrees. See also Turn
.
Luxor.Pen_opacity_random
— MethodPen_opacity_random(t::Turtle)
Change the opacity of the pen to some value at random.
Luxor.Pencolor
— MethodPencolor(t::Turtle, r, g, b)
Set the Red, Green, and Blue colors of the turtle.
Luxor.Pendown
— MethodPendown(t::Turtle)
Put that pen down and start drawing.
Luxor.Penup
— MethodPenup(t::Turtle)
Pick that pen up and stop drawing.
Luxor.Penwidth
— MethodPenwidth(t::Turtle, w)
Set the width of the line drawn.
Luxor.Pop
— MethodPop(t::Turtle)
Lift the turtle's position and orientation off a stack.
Luxor.Push
— MethodPush(t::Turtle)
Save the turtle's position and orientation on a stack.
Luxor.Randomize_saturation
— MethodRandomize_saturation(t::Turtle)
Randomize the saturation of the turtle's pen color.
Luxor.Rectangle
— FunctionRectangle(t::Turtle, width=10.0, height=10.0)
Draw a filled rectangle centered at the current position with the given radius.
Luxor.Reposition
— MethodReposition(t::Turtle, pos::Point)
Reposition(t::Turtle, x, y)
Reposition: pick the turtle up and place it at another position.
Luxor.Towards
— MethodTowards(t::Turtle, pos::Point)
Rotate the turtle to face towards a given point.
Luxor.Turn
— FunctionTurn(t::Turtle, r=5.0)
Increase the turtle's rotation by r
degrees. See also Orientation
.
Luxor._adjust_background_rects
— Method_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" .../>
Luxor._betweenpoly
— Method_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
Luxor._drawing_indices
— MethodLuxor._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
Luxor._empty_neighbourhood
— Methodemptyneighbourhood(sample, w, h, cellsize, d, points, grid)
Uses entries in grid
to check whether the sample
point is more than d
units away from any other point in points
.
The region we're analyzing lies between the origin and Point(w, h)
`.
Luxor._get_drawing_index
— MethodLuxor._get_drawing_index()
Returns the index of the current drawing. If there isn't any drawing yet returns 1.
Luxor._get_next_drawing_index
— MethodLuxor._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.
Luxor._has_drawing
— MethodLuxor._has_drawing()
Returns true if there is a current drawing available or finished, otherwise false.
Luxor._set_drawing_index
— MethodLuxor._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
Luxor._set_next_drawing_index
— MethodLuxor._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.
Luxor._split_string_into_head_mid_tail
— Method_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"
Luxor.add_mesh_patch
— Functionadd_mesh_patch(pattern::Mesh, plist::Array{Point}, colors=Array{Colors.Colorant, 1})
Add a new patch to the mesh pattern in pattern
.
The first three or four sides of the supplied points
polygon define the three or four sides of the mesh shape.
The colors
array define the color of each corner point. Colors are reused if necessary. At least one color should be supplied.
Luxor.add_mesh_patch
— Functionadd_mesh_patch(pattern::Mesh, bezierpath::BezierPath,
colors=Array{Colors.Colorant, 1})
Add a new patch to the mesh pattern in pattern
.
The first three or four elements of the supplied bezierpath
define the three or four sides of the mesh shape.
The colors
array define the color of each corner point. Colors are reused if necessary. At least one color should be supplied.
Use setmesh()
to select the mesh, which will be used to fill shapes.
Luxor.addstop
— Methodaddstop(b::Blend, offset, col)
addstop(b::Blend, offset, (r, g, b, a))
addstop(b::Blend, offset, string)
Add a color stop to a blend. The offset specifies the location along the blend's 'control vector', which varies between 0 (beginning of the blend) and 1 (end of the blend). For linear blends, the control vector is from the start point to the end point. For radial blends, the control vector is from any point on the start circle, to the corresponding point on the end circle.
Examples:
blendredblue = blend(Point(0, 0), 0, Point(0, 0), 1)
addstop(blendredblue, 0, setcolor(sethue("red")..., .2))
addstop(blendredblue, 1, setcolor(sethue("blue")..., .2))
addstop(blendredblue, 0.5, sethue(randomhue()...))
addstop(blendredblue, 0.5, setcolor(randomcolor()...))
Luxor.anglethreepoints
— Methodanglethreepoints(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.
Luxor.animate
— Methodanimate(movie::Movie, scene::Scene; creategif=false, framerate=30)
Create the movie defined in movie
by rendering the frames define in scene
.
Luxor.animate
— Methodanimate(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`)
Luxor.arc
— Methodarc(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()
, ...
Luxor.arc2r
— Method 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.
Luxor.arc2sagitta
— Methodarc2sagitta(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.
Luxor.arrow
— Functionarrow(start::Point, finish::Point, height::Vector, action=:stroke;
keyword arguments...)
Draw a Bézier arrow between start
and finish
, with control points defined to fit in an imaginary box defined by the two supplied height
values (see bezierfrompoints()
). If the height values are different signs, the arrow will change direction on its way.
Keyword arguments are the same as arrow(pt1, pt2, pt3, pt4)
.
Example
arrow(pts[1], pts[end], [15, 15],
decoration = 0.5,
decorate = () -> text(string(pts[1])))
Luxor.arrow
— Functionarrow(start::Point, C1::Point, C2::Point, finish::Point, action=:stroke;
linewidth = 1.0,
arrowheadlength = 10,
arrowheadangle = pi/8,
startarrow = false,
finisharrow = true,
decoration = 0.5,
decorate = nothing
arrowheadfunction = nothing)
Draw a Bezier curved arrow, from start
to finish
, with control points C1
and C2
. Arrow heads can be added/hidden by changing startarrow
and finisharrow
options.
The decorate
keyword argument accepts a function that can execute code at one or more locations on the arrow's shaft. The inherited graphic environment is centered at each point on the shaft given by scalar or vector decoration
, and the x-axis is aligned with the direction of the curve at that point.
Example
This code draws an arrow head that's filled with orange and outlined in green.
function myarrowheadfunction(originalendpoint, newendpoint, shaftangle)
@layer begin
setline(5)
translate(newendpoint)
rotate(shaftangle)
sethue("orange")
ngon(O, 20, 3, 0, :fill)
sethue("green")
ngon(O, 20, 3, 0, :stroke)
end
end
@drawsvg begin
background("white")
arrow(O, 220, 0, π,
linewidth=10,
arrowheadlength=30,
arrowheadangle=π/7,
clockwise=true,
arrowheadfunction = myarrowheadfunction)
end
Luxor.arrow
— Methodarrow(centerpos::Point, radius, startangle, endangle;
linewidth = 1.0,
arrowheadlength = 10,
arrowheadangle = π/8,
decoration = 0.5,
decorate = nothing,
arrowheadfunction = nothing,
clockwise = true)
Draw a curved arrow, an arc centered at centerpos
starting at startangle
and ending at endangle
with an arrowhead at the end. Angles are measured clockwise from the positive x-axis.
Arrows don't use the current linewidth setting (setline()
); you can specify the linewidth.
The decorate
keyword argument accepts a zero-argument function that can execute code at one or more locations on the arrow's shaft. The inherited graphic environment is centered at points on the shaft between 0 and 1 given by scalar or vector decoration
, and the x-axis is aligned with the direction of the curve at that point.
A triangular arrowhead is drawn by default. But you can pass a function to the arrowheadfunction
keyword argument that accepts three arguments: the shaft end, the arrow head end, and the shaft angle. Thsi allows you to draw any shape arrowhead.
Luxor.arrow
— Methodarrow(startpoint::Point, endpoint::Point;
linewidth = 1.0,
arrowheadlength = 10,
arrowheadangle = pi/8,
decoration = 0.5 or range(),
decorate = nothing,
arrowheadfunction = nothing)
Draw a line between two points and add an arrowhead at the end. The arrowhead length will be the length of the side of the arrow's head, and the arrowhead angle is the angle between the sloping side of the arrowhead and the arrow's shaft.
Arrows don't use the current linewidth setting (setline()
), and defaults to 1, but you can specify another value. It doesn't need stroking/filling, the shaft is stroked and the head filled with the current color.
Decoration
The decorate
keyword argument accepts a function with zero arguments that can execute code at one or more locations on the arrow's shaft. The inherited graphic environment is centered at each point on the shaft between 0 and 1 given by scalar or vector decoration
, and the x-axis is aligned with the direction of the curve at that point.
Arrowheads
A triangular arrowhead is drawn by default. But you can pass a function to the arrowheadfunction
keyword argument that accepts three arguments: the shaft end, the arrow head end, and the shaft angle. Thsi allows you to draw any shape arrowhead.
Example
function redbluearrow(shaftendpoint, endpoint, shaftangle)
@layer begin
sethue("red")
sidept1 = shaftendpoint + polar(10, shaftangle + π/2 )
sidept2 = shaftendpoint - polar(10, shaftangle + π/2)
poly([sidept1, endpoint, sidept2], :fill)
sethue("blue")
poly([sidept1, endpoint, sidept2], :stroke, close=false)
end
end
@drawsvg begin
background("white")
arrow(O, O + (120, 120),
linewidth=4,
arrowheadlength=40,
arrowheadangle=π/7,
arrowheadfunction = redbluearrow)
arrow(O, 100, 3π/2, π,
linewidth=4,
arrowheadlength=20,
clockwise=false,arrowheadfunction=redbluearrow)
end 800 250
Luxor.arrowhead
— Functionarrowhead(target[, action=:fill];
shaftangle=0,
headlength=10,
headangle=pi/8)
Draw an arrow head. The arrowhead length will be the length of the side of the arrow's head, and the arrowhead angle is the angle between the sloping side of the arrowhead and the arrow's shaft.
This doesn't use the current linewidth setting (setline()
), and defaults to 1, but you can specify another value.
Luxor.background
— Methodbackground(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.
Luxor.barchart
— Methodbarchart(values;
boundingbox = BoundingBox(O + (-250, -120), O + (250, 120)),
bargap=10,
margin = 5,
border=false,
labels=false,
labelfunction = (values, i, lowpos, highpos, barwidth, scaledvalue) -> begin
label(string(values[i]), :n, highpos, offset=10)
end,
barfunction = (values, i, lowpos, highpos, barwidth, scaledvalue) -> begin
@layer begin
setline(barwidth)
line(lowpos, highpos, :stroke)
end
end)
Draw a barchart where each bar is the height of a value in the values
array. The bars will be scaled to fit in a bounding box.
Text labels are drawn if the keyword labels=true
.
Extended help
The function returns a vector of points; each is the bottom center of a bar.
Draw a Fibonacci sequence as a barchart:
fib(n) = n > 2 ? fib(n - 1) + fib(n - 2) : 1
fibs = fib.(1:15)
@draw begin
fontsize(12)
barchart(fibs, labels=true)
end
To control the drawing of the text and bars, define functions that process the end points:
mybarfunction(values, i, lowpos, highpos, barwidth, scaledvalue)
mylabelfunction(values, i, lowpos, highpos, barwidth, scaledvalue)
and pass them like this:
barchart(vals, barfunction=mybarfunction)
barchart(vals, labelfunction=mylabelfunction)
function myprologfunction(values, basepoint, minbarrange, maxbarrange, barchartheight)
@layer begin
setline(0.2)
for i in 0:10:maximum(values)
rule(boxbottomcenter(basepoint) + (0, -(rescale(i, minbarrange, maxbarrange) * barchartheight)))
end
end
end
Luxor.between
— Functionbetween(bb::BoundingBox, x)
Find a point between the two corners of a BoundingBox corresponding to x
, where x
is typically between 0 and 1.
Luxor.between
— Methodbetween(p1::Point, p2::Point, x)
between((p1::Point, p2::Point), x)
Find the point between point p1
and point p2
for x
, where x
is typically between 0 and 1. between(p1, p2, 0.5)
is equivalent to midpoint(p1, p2)
.
Luxor.bezier
— Methodbezier(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).
Luxor.beziercurvature
— Methodbeziercurvature(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).
Luxor.bezierfrompoints
— Methodbezierfrompoints(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.
Luxor.bezierfrompoints
— Methodbezierfrompoints(ptslist::Array{Point, 1})
Given four points, return the Bézier curve that passes through all four points.
Luxor.bezierpathtopath
— Methodbezierpathtopath(bp::BezierPath)
Convert a Bezier path to a Path object.
@draw drawpath(polytopath(ngon(O, 145, 5, vertices = true)), action = :fill)
Luxor.bezierpathtopoly
— Methodbezierpathtopoly(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.
Luxor.beziersegmentangles
— Methodbeziersegmentangles(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)
Luxor.bezierstroke
— Functionbezierstroke(point1, point2, width=0.0)
Return a BezierPath, a stroked version of a straight line between two points.
It wil have 2 or 6 Bezier path segments that define a brush or pen shape. If width is 0, the brush shape starts and ends at a point. Otherwise the brush shape starts and ends with the thick end.
To draw it, use eg drawbezierpath(..., :fill)
.
Luxor.beziertopoly
— Methodbeziertopoly(bpseg::BezierPathSegment;
steps=10)
Convert a BezierPathsegment to a polygon (an array of points).
Luxor.bezier′
— Methodbezier′(t, A::Point, A1::Point, B1::Point, B::Point)
Return the first derivative of the Bézier function.
Luxor.bezier′′
— Methodbezier′′(t, A::Point, A1::Point, B1::Point, B::Point)
Return the second derivative of Bézier function.
Luxor.bezigon
— Methodbezigon(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.
Luxor.blend
— Methodblend(centerpos1, rad1, centerpos2, rad2, color1, color2)
Create a radial blend.
Example:
redblue = blend(
pos, 0, # first circle center and radius
pos, tiles.tilewidth/2, # second circle center and radius
"red",
"blue"
)
Luxor.blend
— Methodblend(from::Point, startradius, to::Point, endradius)
Create an empty radial blend.
Radial blends are defined by two circles that define the start and stop locations. The first point is the center of the start circle, the first radius is the radius of the first circle.
A new blend is empty. To add colors, use addstop()
.
Luxor.blend
— Methodblend(pt1::Point, pt2::Point, color1, color2)
Create a linear blend.
Example:
redblue = blend(pos, pos, "red", "blue")
Luxor.blend
— Methodblend(from::Point, to::Point)
Create an empty linear blend.
A blend is a specification of how one color changes into another. Linear blends are defined by two points: parallel lines through these points define the start and stop locations of the blend. The blend is defined relative to the current axes origin. This means that you should be aware of the current axes when you define blends, and when you use them.
To add colors, use addstop()
.
Luxor.blendadjust
— Functionblendadjust(ablend, center::Point, xscale, yscale, rot=0)
Modify an existing blend by scaling, translating, and rotating it so that it will fill a shape properly even if the position of the shape is nowhere near the original location of the blend's definition.
For example, if your blend definition was this (notice the 1
)
blendgoldmagenta = blend(
Point(0, 0), 0, # first circle center and radius
Point(0, 0), 1, # second circle center and radius
"gold",
"magenta"
)
you can use it in a shape that's 100 units across and centered at pos
, by calling this:
blendadjust(blendgoldmagenta, Point(pos.x, pos.y), 100, 100)
then use setblend()
:
setblend(blendgoldmagenta)
Luxor.blendmatrix
— Methodblendmatrix(b::Blend, m)
Set the matrix of a blend.
To apply a sequence of matrix transforms to a blend:
A = [1 0 0 1 0 0]
Aj = cairotojuliamatrix(A)
Sj = scalingmatrix(2, .2) * Aj
Tj = translationmatrix(10, 0) * Sj
A1 = juliatocairomatrix(Tj)
blendmatrix(b, As)
Luxor.boundingboxesintersect
— Methodboundingboxesintersect(bbox1::BoundingBox, bbox2::BoundingBox)
boundingboxesintersect(acorner1::Point, acorner2::Point, bcorner1::Point, bcorner2::Point)
Return true if the two bounding boxes intersect.
Luxor.box
— Methodbox(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.
Luxor.box
— Methodbox(bbox::BoundingBox, cornerradii::Array;
action = :none)
Make a box that is the size of the BoundingBox bbox
with curved corners.
Luxor.box
— Methodbox(bbox::BoundingBox, cornerradius::Real;
action = :none)
Make a box that is the size of the BoundingBox bbox
with curved corners.
Luxor.box
— Methodbox(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.
Luxor.box
— Methodbox(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()
.
Luxor.box
— Methodbox(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
Luxor.box
— Methodbox(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.
Luxor.box
— Methodbox(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.
Luxor.box
— Methodbox(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).
Luxor.box
— Methodbox(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
.
Luxor.box
— Methodbox(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
.
Luxor.box
— Methodbox(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
.
Luxor.boxaspectratio
— Functionboxaspectratio(bb::BoundingBox=BoundingBox())
Return the aspect ratio (the height divided by the width) of bounding box bb
.
Luxor.boxbottomcenter
— Functionboxbottomcenter(bb::BoundingBox=BoundingBox())
Return the point at the bottom center of the BoundingBox bb
, defaulting to the drawing extent.
⋅ ⋅ ⋅
⋅ ⋅ ⋅
⋅ ■ ⋅
Luxor.boxbottomleft
— Functionboxbottomleft(bb::BoundingBox=BoundingBox())
Return the point at the bottom left of the BoundingBox bb
, defaulting to the drawing extent.
⋅ ⋅ ⋅
⋅ ⋅ ⋅
■ ⋅ ⋅
Luxor.boxbottomright
— Functionboxbottomright(bb::BoundingBox=BoundingBox())
Return the point at the bottom right of the BoundingBox bb
, defaulting to the drawing extent.
⋅ ⋅ ⋅
⋅ ⋅ ⋅
⋅ ⋅ ■
Luxor.boxdiagonal
— Functionboxdiagonal(bb::BoundingBox=BoundingBox())
Return the length of the diagonal of bounding box bb
.
Luxor.boxheight
— Functionboxheight(bb::BoundingBox=BoundingBox())
Return the height of bounding box bb
.
Luxor.boxmap
— Methodboxmap(A::Array, pt, w, h)
Build a box map of the values in A
with one corner at pt
and width w
and height h
. There are length(A)
boxes. The areas of the boxes are proportional to the original values, scaled as necessary.
The return value is an array of BoxmapTiles. For example:
[BoxmapTile(0.0, 0.0, 10.0, 20.0)
BoxmapTile(10.0, 0.0, 10.0, 13.3333)
BoxmapTile(10.0, 13.3333, 10.0, 6.66667)]
with each tile containing (x, y, w, h)
. box()
and BoundingBox()
can work with BoxmapTiles as well.
Example
using Luxor
@svg begin
fontsize(16)
fontface("HelveticaBold")
pt = Point(-200, -200)
a = rand(10:200, 15)
tiles = boxmap(a, Point(-200, -200), 400, 400)
for (n, t) in enumerate(tiles)
randomhue()
bb = BoundingBox(t)
box(bb - 2, :stroke)
box(bb - 5, :fill)
sethue("white")
text(string(n), midpoint(bb[1], bb[2]), halign=:center)
end
end 400 400 "boxmap.svg"
Luxor.boxmiddlecenter
— Functionboxmiddlecenter(bb::BoundingBox=BoundingBox())
Return the point at the center of the BoundingBox bb
, defaulting to the drawing extent.
⋅ ⋅ ⋅
⋅ ■ ⋅
⋅ ⋅ ⋅
Luxor.boxmiddleleft
— Functionboxmiddleleft(bb::BoundingBox=BoundingBox())
Return the point at the middle left of the BoundingBox bb
, defaulting to the drawing extent.
⋅ ⋅ ⋅
■ ⋅ ⋅
⋅ ⋅ ⋅
Luxor.boxmiddleright
— Functionboxmiddleright(bb::BoundingBox=BoundingBox())
Return the point at the midde right of the BoundingBox bb
, defaulting to the drawing extent.
⋅ ⋅ ⋅
⋅ ⋅ ■
⋅ ⋅ ⋅
Luxor.boxtopcenter
— Functionboxtopcenter(bb::BoundingBox=BoundingBox())
Return the point at the top center of the BoundingBox bb
, defaulting to the drawing extent.
⋅ ■ ⋅
⋅ ⋅ ⋅
⋅ ⋅ ⋅
Luxor.boxtopleft
— Functionboxtopleft(bb::BoundingBox=BoundingBox())
Return the point at the top left of the BoundingBox bb
, defaulting to the drawing extent.
■ ⋅ ⋅
⋅ ⋅ ⋅
⋅ ⋅ ⋅
Luxor.boxtopright
— Functionboxtopright(bb::BoundingBox=BoundingBox())
Return the point at the top right of the BoundingBox bb
, defaulting to the drawing extent.
⋅ ⋅ ■
⋅ ⋅ ⋅
⋅ ⋅ ⋅
Luxor.boxwidth
— Functionboxwidth(bb::BoundingBox=BoundingBox())
Return the width of bounding box bb
.
Luxor.brush
— Functionbrush(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.
There is a lot of randomness in this function. Results are unpredictable.
Luxor.buildcolumn
— Methodbuildcolumn(A, x, y, w, h)
Make a column of tiles from A.
Luxor.buildrow
— Methodbuildrow(A, x, y, w, h)
Make a row of tiles from A.
Luxor.cairotojuliamatrix
— Methodcairotojuliamatrix(c)
Return a 3x3 Julia matrix that's the equivalent of the six-element matrix in c
.
See getmatrix()
for details.
Luxor.carc
— Methodcarc(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()
, ...
Luxor.carc2r
— Methodcarc2r(c1::Point, p2::Point, p3::Point; action=:none)
Add a circular arc centered at c1
that starts at p2
and ends at p3
, going counterclockwise, to the current path.
c1
-p2
really determines the radius. If p3
doesn't lie on the circular path, it will be used only as an indication of the arc's length, rather than its position.
Luxor.carc2sagitta
— Methodcarc2sagitta(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.
Luxor.center3pts
— Methodcenter3pts(a::Point, b::Point, c::Point)
Find the radius and center point for three points lying on a circle.
returns (centerpoint, radius)
of a circle.
If there's no such circle, the function returns (Point(0, 0), 0)
.
If two of the points are the same, use circle(pt1, pt2)
instead.
Luxor.circle
— Methodcircle(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.
Luxor.circle
— Methodcircle(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.
Luxor.circle
— Methodcircle(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.
Luxor.circlecircleinnertangents
— Methodcirclecircleinnertangents(circle1center::Point, circle1radius, circle2center::Point, circle2radius)
Find the inner tangents of two circles. These are tangent lines that cross as they skim past one circle and touch the other.
Returns the four points: tangentpoint1 on circle 1, tangentpoint1 on circle2, tangentpoint2 on circle 1, tangentpoint2 on circle2.
Returns (O, O, O, O)
if inner tangents can't be found (eg when the circles overlap).
Use circlecircleoutertangents()
to find the outer tangents.
Luxor.circlecircleoutertangents
— Methodcirclecircleoutertangents(cpt1::Point, r1, cpt2::Point, r2)
Return four points, p1
, p2,
p3,
p4, where a line through
p1and
p2, and a line through
p3and
p4, form the outer tangents to the circles defined by
cpt1/r1and
cpt2/r2`.
Returns four identical points (O
) if one of the circles lies inside the other.
Luxor.circlepath
— Methodcirclepath(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.
Luxor.circlepointtangent
— Methodcirclepointtangent(through::Point, radius, targetcenter::Point, targetradius)
Find the centers of up to two circles of radius radius
that pass through point through
and are tangential to a circle that has radius targetradius
and center targetcenter
.
This function returns a tuple:
(0, O, O) - no circles exist
(1, pt1, O) - 1 circle exists, centered at pt1
(2, pt1, pt2) - 2 circles exist, with centers at pt1 and pt2
(The O are just dummy points so that three values are always returned.)
Luxor.circlering
— Functioncirclering(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
Luxor.circletangent2circles
— Methodcircletangent2circles(radius, circle1center::Point, circle1radius, circle2center::Point, circle2radius)
Find the centers of up to two circles of radius radius
that are tangent to the two circles defined by circle1...
and circle2...
. These two circles can overlap, but one can't be inside the other.
(0, O, O) - no such circles exist
(1, pt1, O) - 1 circle exists, centered at pt1
(2, pt1, pt2) - 2 circles exist, with centers at pt1 and pt2
(The O are just dummy points so that three values are always returned.)
Luxor.clip
— Methodclip()
Establish a new clipping region by intersecting the current clipping region with the current path and then clearing the current path.
An existing clipping region is enforced through and after a gsave()
-grestore()
block, but a clipping region set inside a gsave()
-grestore()
block is lost after grestore()
. [?]
Luxor.clippreserve
— Methodclippreserve()
Establish a new clipping region by intersecting the current clipping region with the current path, but keep the current path.
Luxor.clipreset
— Methodclipreset()
Reset the clipping region to the current drawing's extent.
Luxor.closepath
— Methodclosepath()
Draw a line from the current point to the first point of the current subpath. This is Cairo's close_path()
function.
Luxor.crescent
— Methodcrescent(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)
Luxor.crescent
— Methodcrescent(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)
Luxor.cropmarks
— Methodcropmarks(center, width, height)
Draw cropmarks (also known as trim marks). Use current color.
Luxor.crossproduct
— Methodcrossproduct(p1::Point, p2::Point)
This is the perp dot product, really, not the crossproduct proper (which is 3D):
Luxor.currentdrawing
— Methodcurrentdrawing(d::Drawing)
Sets and returns the current Luxor drawing overwriting an existing drawing if exists.
Luxor.currentdrawing
— Methodcurrentdrawing()
Return the current Luxor drawing, if there currently is one.
Luxor.currentpoint
— Methodcurrentpoint()
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()
.
Luxor.curve
— Methodcurve(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
).
Luxor.determinant3
— Methoddeterminant3(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.
Luxor.dimension
— Methoddimension(p1::Point, p2::Point;
format::Function = (d) -> string(d), # process the measured value into a string
offset = 0.0, # left/right, parallel with x axis
fromextension = (10.0, 10.0), # length of extensions lines left and right
toextension = (10.0, 10.0), #
textverticaloffset = 0.0, # range 1.0 (top) to -1.0 (bottom)
texthorizontaloffset = 0.0, # range 1.0 (top) to -1.0 (bottom)
textgap = 5, # gap between start of each arrow (≈ fontsize?)
textrotation = 0.0,
arrowlinewidth = 1.0,
arrowheadlength = 10,
arrowheadangle = π/8)
Calculate and draw dimensioning graphics for the distance between p1
and p2
. The value can be formatted with function format
.
p1
is the lower on the page (ie probably the higher y value) point, p2
is the higher on the page (ie probably lower y) point.
offset
is to the left (-x) when negative.
Dimension graphics will be rotated to align with a line between p1
and p2
.
In textverticaloffset
, "vertical" and "horizontal" are best understood by "looking" along the line from the first point to the second. textverticaloffset
ranges from -1 to 1, texthorizontaloffset
in default units.
toextension
[5 , 5]
<---> <--->
to
----------- +
^
|
-50
|
v
---------- +
from
<---> <--->
[5 , 5]
fromextension
<---------------->
offset
Returns the measured distance and the text.
Luxor.distance
— Methoddistance(h1::Hexagon, h2::Hexagon)
Find distance between hexagons h1 and h2.
Luxor.distance
— Methoddistance(p1::Point, p2::Point)
Find the distance between two points (two argument form).
Luxor.do_action
— Methoddo_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.
Luxor.dotproduct
— Methoddotproduct(a::Point, b::Point)
Return the scalar dot product of the two points.
Luxor.douglas_peucker
— MethodUse a non-recursive Douglas-Peucker algorithm to simplify a polygon. Used by simplify()
.
douglas_peucker(pointlist::Array, start_index, last_index, epsilon)
Luxor.drawbezierpath
— Methoddrawbezierpath(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.
Luxor.drawbezierpath
— Methoddrawbezierpath(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.
Luxor.drawpath
— Methoddrawpath(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.
Luxor.drawpath
— Methoddrawpath(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.
Luxor.easeincirc
— Methodeaseincirc(t, b, c, d)
circular easing in - accelerating from zero velocity
Luxor.easeincubic
— Methodeaseincubic(t, b, c, d)
cubic easing in - accelerating from zero velocity
Luxor.easeinexpo
— Methodeaseinexpo(t, b, c, d)
exponential easing in - accelerating from zero velocity
Luxor.easeinoutbezier
— Functioneaseinoutbezier(t, b, c, d, cpt1, cpt2)
This easing function takes six arguments, the usual t
, b
, c
, and d
, but also two points. These are the normalized control points of a Bezier curve drawn between Point(0, 0)
to Point(1.0, 1.0)
. The y
value of the Bezier is the eased value for t
.
In your frame()
generating function, if a Scene specifies the easeinoutbezier
easing function, you can use this:
...
lineareasing = rescale(framenumber, 1, scene.framerange.stop)
beziereasing = scene.easingfunction(lineareasing, 0, 1, 1,
Point(0.25, 0.25), Point(0.75, 0.75))
...
These two control points lie on the line between 0/0
and 1/1
, so it's equivalent to a linear easing (lineartween()
or easingflat
).
However, in the next example, the two control points define a wave-like curve that changes direction before changing back. When animating with this easing function, an object will 'go retrograde' for a while.
lineareasing = rescale(framenumber, 1, scene.framerange.stop)
beziereasing = scene.easingfunction(lineareasing, 0, 1, 1,
Point(0.01, 1.99), Point(0.99, -1.5))
Luxor.easeinoutcirc
— Methodeaseinoutcirc(t, b, c, d)
circular easing in/out - acceleration until halfway, then deceleration
Luxor.easeinoutcubic
— Methodeaseinoutcubic(t, b, c, d)
cubic easing in/out - acceleration until halfway, then deceleration
Luxor.easeinoutexpo
— Methodeaseinoutexpo(t, b, c, d)
exponential easing in/out - accelerating until halfway, then decelerating
Luxor.easeinoutinversequad
— Methodeaseinoutinversequad(t, b, c, d)
ease in, then slow down, then speed up, and ease out
Luxor.easeinoutquad
— Methodeaseinoutquad(t, b, c, d)
quadratic easing in/out - acceleration until halfway, then deceleration
Luxor.easeinoutquart
— Methodeaseinoutquart(t, b, c, d)
quartic easing in/out - acceleration until halfway, then deceleration
Luxor.easeinoutquint
— Methodeaseinoutquint(t, b, c, d)
quintic easing in/out - acceleration until halfway, then deceleration
Luxor.easeinoutsine
— Methodeaseinoutsine(t, b, c, d)
sinusoidal easing in/out - accelerating until halfway, then decelerating
Luxor.easeinquad
— Methodeaseinquad(t, b, c, d)
quadratic easing in - accelerating from zero velocity
Luxor.easeinquart
— Methodeaseinquart(t, b, c, d)
quartic easing in - accelerating from zero velocity
Luxor.easeinquint
— Methodeaseinquint(t, b, c, d)
quintic easing in - accelerating from zero velocity
Luxor.easeinsine
— Methodeaseinsine(t, b, c, d)
sinusoidal easing in - accelerating from zero velocity
Luxor.easeoutcirc
— Methodeaseoutcirc(t, b, c, d)
circular easing out - decelerating to zero velocity
Luxor.easeoutcubic
— Methodeaseoutcubic(t, b, c, d)
cubic easing out - decelerating to zero velocity
Luxor.easeoutexpo
— Methodeaseoutexpo(t, b, c, d)
exponential easing out - decelerating to zero velocity
Luxor.easeoutquad
— Methodeaseoutquad(t, b, c, d)
quadratic easing out - decelerating to zero velocity
Luxor.easeoutquart
— Methodeaseoutquart(t, b, c, d)
quartic easing out - decelerating to zero velocity
Luxor.easeoutquint
— Methodeaseoutquint(t, b, c, d)
quintic easing out - decelerating to zero velocity
Luxor.easeoutsine
— Methodeaseoutsine(t, b, c, d)
sinusoidal easing out - decelerating to zero velocity
Luxor.easingflat
— Methodeasingflat(t, b, c, d)
A flat easing function, same as lineartween()
.
For all easing functions, the four parameters are:
t
time, ie the current framenumberb
beginning position or bottom value of the rangec
total change in position or top value of the ranged
duration, ie a framecount
t/d
ort/=d
normalizest
to between 0 and 1... * c
scales up to the required range value... + b
adds the initial offset
Luxor.ellipse
— Methodellipse(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.
Luxor.ellipse
— Methodellipse(focus1::Point, focus2::Point, pt::Point;
action=:none,
stepvalue=pi/100,
vertices=false,
reversepath=false)
Build a polygon approximation to an ellipse, given two points and a point somewhere on the ellipse.
Luxor.ellipse
— Methodellipse(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()
...
Luxor.ellipseinquad
— Methodellipseinquad(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 centerellipsesemimajor
ellipse semimajor axisellipsesemiminor
ellipse semiminor axisellipseangle
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
Luxor.epitrochoid
— Methodepitrochoid(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
).
Luxor.fillpath
— Methodfillpath()
Fill the current path according to the current settings. The current path is then emptied.
Luxor.fillpreserve
— Methodfillpreserve()
Fill the current path with current settings, but then keep the path current.
Luxor.fillstroke
— Methodfillstroke()
Fill and stroke the current path. After this, the current path is empty, and there is no current point.
Luxor.findbeziercontrolpoints
— Methodfindbeziercontrolpoints(
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
.
Luxor.finish
— Methodfinish(;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
Luxor.fontface
— Methodfontface(fontname)
Select a font to use. (Toy API)
Luxor.fontsize
— Methodfontsize(n)
Set the font size to n
units. The default size is 10 units. (Toy API)
Luxor.get_bezier_length
— Methodget_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. :(
Luxor.get_bezier_points
— Methodget_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.
Luxor.get_current_color
— Methodgetcurrentcolor()
Return an RGBA colorant, the current color, as set by setcolor
etc...
Luxor.get_current_hue
— Methodgetcurrenthue()
As set by eg sethue()
. Return an RGB colorant.
Luxor.get_fontsize
— Methodget_fontsize()
Return the font size set by fontsize
or. more precisely. the y-scale of the Cairo font matrix if Cairo.set_font_matrix
is used directly. (Toy API)
This only works if Cairo is at least at v1.0.5.
Luxor.getcolor
— Methodgetcolor()
Return an RGBA colorant, the current color, as set by setcolor
etc...
Luxor.getfillrule
— Methodgetfillrule()
Get the current fill rule for paths for the current drawing.
The rule
can be :winding
or :even_odd
.
See setfillrule
.
Luxor.getline
— Methodgetline()
Get the current line width, in points.
Use setline()
to set the value.
Luxor.getmatrix
— Methodgetmatrix()
Get the current workspace (position, scale, and orientation) as a 6-element vector:
[xx, yx, xy, yy, x0, y0]
xx
component of the affine transformationyx
component of the affine transformationxy
component of the affine transformationyy
component of the affine transformationx0
translation component of the affine transformationy0
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
Luxor.getmode
— Methodgetmode()
Return the current compositing/blending mode as a string.
Luxor.getnearestpointonline
— Methodgetnearestpointonline(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()
.
Luxor.getpath
— Methodgetpath()
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
Luxor.getpathflat
— Methodgetpathflat()
Get the current path, like getpath()
but flattened so that there are no Bézier curves.
Returns a CairoPath which is an array of element_type
and points
objects.
Luxor.getrotation
— Methodgetrotation(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.
Luxor.getscale
— Methodgetscale(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.
Luxor.gettranslation
— Methodgettranslation(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.
Luxor.getworldposition
— Functiongetworldposition(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)
Luxor.grestore
— Methodgrestore()
Replace the current graphics state with the one previously saved by the most recent gsave()
.
Luxor.gsave
— Methodgsave()
Save the current graphics environment, including current color settings.
Luxor.hascurrentpoint
— Methodhascurrentpoint()
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.
Luxor.hexagons_within
— Methodhexagons_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.
Luxor.hexcenter
— Methodhexcenter(hex::Hexagon)
Find the center of the hex
hexagon. Returns a Point.
Luxor.hexcube_linedraw
— Methodhexcube_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.
Luxor.hexcube_round
— Functionhexcube_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.
Luxor.hexdiagonals
— Methodhexdiagonals(hex::Hexagon)
Return the six hexagons that lie on the diagonals to hex
.
Luxor.hexnearest_cubic
— Method 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
.
Luxor.hexneighbors
— Methodhexneighbors(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)
Luxor.hexring
— Methodhexring(n::Int, hex::Hexagon)
Return the ring of hexagons that surround hex
. If n
is 1, the hexagons immediately surrounding hex
are returned.
Luxor.hexspiral
— Methodhexspiral(hex, n)
Return an array of hexagons to spiral around a central hexagon forming n
rings.
Luxor.hextile
— Method hextile(hex::Hexagon)
Calculate the six vertices of the hexagon hex
and return them in an array of Points.
Luxor.highestaspectratio
— Methodhighestaspectratio()
Find the highest aspect ratio of a list of rectangles, given the length of the side along which they are to be laid out.
Luxor.highlightcells
— Functionhighlightcells(t::Table, cellnumbers, action::Symbol=:stroke;
color::Colorant=colorant"red",
offset = 0)
Highlight (draw or fill) one or more cells of table t
. cellnumbers
is a range, array, or an array of row/column tuples.
highlightcells(t, 1:10, :fill, color=colorant"blue")
highlightcells(t, vcat(1:5, 150), :stroke, color=colorant"magenta")
highlightcells(t, [(4, 5), (3, 6)])
Luxor.hypotrochoid
— Methodhypotrochoid(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
).
Luxor.image_as_matrix!
— Methodimage_as_matrix!(buffer)
Like image_as_matrix()
, but use an existing UInt32 buffer.
buffer
is a buffer of UInt32.
w = 200
h = 150
buffer = zeros(UInt32, w, h)
Drawing(w, h, :image)
origin()
juliacircles(50)
m = image_as_matrix!(buffer)
finish()
# collect(m)) is Array{ARGB32,2}
Images.RGB.(m)
Luxor.image_as_matrix
— Methodimage_as_matrix()
Return an Array of the current state of the picture as an array of ARGB32.
A matrix 50 wide and 30 high => a table 30 rows by 50 cols
using Luxor, Images
Drawing(50, 50, :png)
origin()
background(randomhue()...)
sethue("white")
fontsize(40)
fontface("Georgia")
text("42", halign=:center, valign=:middle)
mat = image_as_matrix()
finish()
Luxor.initnoise
— Methodinitnoise(seed::Int)
initnoise()
Initialize the noise generation code.
julia> initnoise(); noise(1)
0.7453148982810598
julia> initnoise(); noise(1)
0.7027617067916981
If you provide an integer seed, it will be used to seed Random.seed!()
` when the noise code is initialized:
julia> initnoise(41); noise(1) # yesterday
0.7134000046640385
julia> initnoise(41); noise(1) # today
0.7134000046640385
If you need to control which type of random number generator is used, you can provide your own and it will be used instead of the default Julia implementation.
julia> rng = MersenneTwister(1234) # any AbstractRNG
julia> initnoise(rng)
Luxor.insertvertices!
— Methodinsertvertices!(pgon;
ratio=0.5)
Insert a new vertex into each edge of a polygon pgon
. The default ratio
of 0.5 divides the original edge of the polygon into half.
Luxor.intersectboundingboxes
— Methodintersectboundingboxes(bb1::BoundingBox, bb2::BoundingBox)
Return a BoundingBox that's an intersection of the two bounding boxes.
Luxor.intersection2circles
— Methodintersection2circles(pt1, r1, pt2, r2)
Find the area of intersection between two circles, the first centered at pt1
with radius r1
, the second centered at pt2
with radius r2
.
If one circle is entirely within another, that circle's area is returned.
Luxor.intersectioncirclecircle
— Methodintersectioncirclecircle(cp1, r1, cp2, r2)
Find the two points where two circles intersect, if they do. The first circle is centered at cp1
with radius r1
, and the second is centered at cp1
with radius r1
.
Returns
(flag, ip1, ip2)
where flag
is a Boolean true
if the circles intersect at the points ip1
and ip2
. If the circles don't intersect at all, or one is completely inside the other, flag
is false
and the points are both Point(0, 0).
Use intersection2circles()
to find the area of two overlapping circles.
In the pure world of maths, it must be possible that two circles 'kissing' only have a single intersection point. At present, this unromantic function reports that two kissing circles have no intersection points.
Luxor.intersectionlinecircle
— Methodintersectionlinecircle(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
, or2
pt1
is first intersection point, orPoint(0, 0)
if nonept2
is the second intersection point, orPoint(0, 0)
if none
The calculated intersection points won't necessarily lie on the line segment between p1
and p2
.
Luxor.intersectionlines
— Methodintersectionlines(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.
Luxor.intersectlinepoly
— Methodintersectlinepoly(pt1::Point, pt2::Point, C)
Return an array of the points where a line between pt1 and pt2 crosses polygon C.
Luxor.isarcclockwise
— Methodisarcclockwise(c::Point, A::Point, B::Point)
Return true
if an arc centered at c
going from A
to B
is clockwise.
If c
, A
, and B
are collinear, then a hemispherical arc could be either clockwise or not.
Luxor.isinside
— Methodisinside(p::Point, bb:BoundingBox)
Returns true
if pt
is inside bounding box bb
.
Luxor.isinside
— Methodisinside(p, pol; allowonedge=false)
Is a point p
inside a polygon pol
? Returns true if it does, or false.
This is an implementation of the Hormann-Agathos (2001) Point in Polygon algorithm.
The classification of points lying on the edges of the target polygon, or coincident with its vertices is not clearly defined, due to rounding errors or arithmetical inadequacy. By default these will generate errors, but you can suppress these by setting allowonedge
to true
.
Luxor.ispointonleftofline
— Methodispointonleftofline(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
Luxor.ispointonline
— Methodispointonline(pt::Point, pt1::Point, pt2::Point;
extended = false,
atol = 10E-5)
Return true
if the point pt
lies on a straight line between pt1
and pt2
.
If extended
is false (the default) the point must lie on the line segment between pt1
and pt2
. If extended
is true, the point lies on the line if extended in either direction.
Luxor.ispointonpoly
— Methodispointonpoly(pt::Point, pgon;
atol=10E-5)
Return true
if pt
lies on the polygon pgon.
Luxor.ispolyclockwise
— Methodispolyclockwise(pgon)
Returns true if polygon is clockwise. WHEN VIEWED IN A LUXOR DRAWING...?
TODO This code is still experimental...
Luxor.ispolyconvex
— Methodispolyconvex(pts)
Return true if polygon is convex. This tests that every interior angle is less than or equal to 180°.
Luxor.juliacircles
— Functionjuliacircles(radius=100;
outercircleratio=0.75,
innercircleratio=0.65,
action=:fill)
Draw the three Julia circles ("dots") in color centered at the origin.
The distance of the centers of each circle from the origin is radius
.
The optional keyword argument outercircleratio
(default 0.75) determines the radius of each circle relative to the main radius. So the default is to draw circles of radius 75 points around a larger circle of radius 100.
Return the three centerpoints.
The innercircleratio
(default 0.65) no longer does anything useful (it used to draw the smaller circles) and will be deprecated.
Luxor.julialogo
— Methodjulialogo(;
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.
Luxor.juliatocairomatrix
— Methodjuliatocairomatrix(c)
Return a six-element matrix that's the equivalent of the 3x3 Julia matrix in c
.
See getmatrix()
for details.
Luxor.label
— Functionlabel(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.
Luxor.label
— Functionlabel(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
Luxor.latexboundingbox
— Functionlatexboundingbox(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.
Luxor.latextextsize
— Methodlatextextsize(lstr::LaTeXString)
Returns the width and height of a latex string.
Luxor.layout
— Methodlayout(A, x, y, w, h)
From A, make a row of tiles (if wider than tall) or a column of tiles (if taller than wide).
Luxor.line
— Methodline(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()
.
Luxor.line
— Methodline(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.
Luxor.lineartween
— Methoddefault linear transition - no easing, no acceleration
Luxor.makebezierpath
— Methodmakebezierpath(pgon::Array{Point, 1};
smoothing=1.0)
Return a Bézier path (a BezierPath) that represents a polygon (an array of points). The Bézier path is an array of segments (tuples of 4 points); each segment contains the four points that make up a section of the entire Bézier path.
smoothing
determines how closely the curve follows the polygon. A value of 0 returns a straight-sided path; as values move above 1 the paths deviate further from the original polygon's edges.
Luxor.mask
— Methodmask(point::Point, focus::Point, width, height)
max = 1.0,
min = 0.0,
easingfunction = easingflat)
Calculate a value between 0 and 1 for a point
relative to a rectangular area defined by focus
, width
, and height
. The value will approach max
(1.0) at the center, and min
(0.0) at the edges.
Luxor.mask
— Methodmask(point::Point, focus::Point, radius)
max = 1.0,
min = 0.0,
easingfunction = easingflat)
Calculate a value between 0 and 1 for a point
relative to a circular area defined by focus
and radius
. The value will approach max
(1.0) at the center of the circular area, and min
(0.0) at the circumference.
Luxor.mesh
— Functionmesh(points::Array{Point},
colors=Array{Colors.Colorant, 1})
Create a mesh.
The first three or four sides of the supplied points
polygon define the three or four sides of the mesh shape.
The colors
array define the color of each corner point. Colors are reused if necessary. At least one color should be supplied.
Example
@svg begin
pl = ngon(O, 250, 3, pi/6, vertices=true)
mesh1 = mesh(pl, [
"purple",
Colors.RGBA(0.0, 1.0, 0.5, 0.5),
"yellow"
])
setmesh(mesh1)
setline(180)
ngon(O, 250, 3, pi/6, :strokepreserve)
setline(5)
sethue("black")
strokepath()
end
Luxor.mesh
— Functionmesh(bezierpath::BezierPath,
colors=Array{Colors.Colorant, 1})
Create a mesh. The first three or four elements of the supplied bezierpath
define the three or four sides of the mesh shape.
The colors
array define the color of each corner point. Colors are reused if necessary. At least one color should be supplied.
Use setmesh()
to select the mesh, which will be used to fill shapes.
Example
@svg begin
bp = makebezierpath(ngon(O, 50, 4, 0, vertices=true))
mesh1 = mesh(bp, [
"red",
Colors.RGB(0, 1, 0),
Colors.RGB(0, 1, 1),
Colors.RGB(1, 0, 1)
])
setmesh(mesh1)
box(O, 500, 500, :fill)
end
Luxor.midpoint
— Functionmidpoint(bb::BoundingBox=BoundingBox())
Returns the point midway between the two points of the BoundingBox. This should also be the center, unless I've been very stupid...
Luxor.midpoint
— Methodmidpoint(a)
Find midpoint between the first two elements of an array of points.
Luxor.midpoint
— Methodmidpoint(p1, p2)
Find the midpoint between two points.
Luxor.move
— Methodmove(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.
Luxor.nearestindex
— Methodnearestindex(polydistancearray, value)
Return a tuple of the index of the largest value in polydistancearray
less than value
, and the difference value. Array is assumed to be sorted.
(Designed for use with polydistances()
).
Luxor.newpath
— Methodnewpath()
Discard the current path's contents. After this, the current path is empty, and there's no current point.
Luxor.newsubpath
— Methodnewsubpath()
Start a new subpath in the current path. After this, there's no current point.
Luxor.nextgridpoint
— Methodnextgridpoint(g::GridHex)
Returns the next available grid point of a hexagonal grid.
Luxor.nextgridpoint
— Methodnextgridpoint(g::GridRect)
Returns the next available (or even the first) grid point of a grid.
Luxor.ngon
— Functionngon(x, y, radius, sides=5, orientation=0;
action = :none,
vertices = false,
reversepath = false)
Draw a regular polygon centered at point Point(x,y)
.
Luxor.ngon
— Functionngon(centerpos, radius, sides=5, orientation=0;
action=:none,
vertices=false,
reversepath=false)
Draw a regular polygon centered at point centerpos
.
Find the vertices of a regular n-sided polygon centered at x
, y
with circumradius radius
.
The polygon is constructed counterclockwise, starting with the first vertex drawn below the positive x-axis.
If you just want the raw points, use keyword argument vertices=true
, which returns the array of points. Compare:
ngon(0, 0, 4, 4, 0, vertices=true) # returns the polygon's points:
4-element Array{Luxor.Point, 1}:
Luxor.Point(2.4492935982947064e-16, 4.0)
Luxor.Point(-4.0, 4.898587196589413e-16)
Luxor.Point(-7.347880794884119e-16, -4.0)
Luxor.Point(4.0, -9.797174393178826e-16)
whereas
ngon(0, 0, 4, 4, 0, :close) # draws a polygon
Luxor.ngonside
— Functionngonside(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
.
Luxor.noise
— Methodnoise(x) ; detail = 1, persistence = 1.0) # 1D
noise(x, y) ; detail = 1, persistence = 1.0) # 2D
noise(x, y, z) ; detail = 1, persistence = 1.0) # 3D
noise(x, y, z, w) ; detail = 1, persistence = 1.0) # 4D
Generate a noise value between 0.0 and 1.0 corresponding to the x
, y
, z
, and w
values. An x
value on its own produces 1D noise, x
and y
make 2D noise, and so on.
The detail
value is an integer (>= 1) specifying how many octaves of noise you want.
The persistence
value, typically between 0.0 and 1.0, controls how quickly the amplitude diminishes for each successive octave for values of detail
greater than 1.
Luxor.offsetlinesegment
— Methodoffsetlinesegment(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()
.
Luxor.offsetpoly
— Methodoffsetpoly(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!
Luxor.offsetpoly
— Methodoffsetpoly(plist;
startoffset = 10,
endoffset = 10,
easingfunction = lineartween)
Return a closed polygon that is offset from and encloses an open polygon.
The incoming set of points plist
is treated as an open polygon, and another set of points is created, which form a polygon lying ...offset
units away from the source poly.
This method for offsetpoly()
treats the list of points as n
vertices connected with n - 1
lines. It allows you to vary the offset from the start of the line to the end.
The other method offsetpoly(plist, d)
treats the list of points as n
vertices connected with n
lines.
Extended help
This function accepts a keyword argument that allows you to control the offset using a function, using the easing functionality built in to Luxor. By default the function is lineartween()
, so the offset changes linearly between the startoffset
and the endoffset
. The function:
f(a, b, c, d) = 2sin((a * π))
runs from 0 to 2 and back as a
runs from 0 to 1. The offsets are scaled by this amount.
Luxor.offsetpoly
— Methodoffsetpoly(plist::Array{Point, 1}, d) where T<:Number
Return a polygon that is offset from a polygon by d
units.
The incoming set of points plist
is treated as a polygon, and another set of points is created, which form a polygon lying d
units away from the source poly.
Polygon offsetting is a topic on which people have written PhD theses and published academic papers, so this short brain-dead routine will give good results for simple polygons up to a point (!). There are a number of issues to be aware of:
very short lines tend to make the algorithm 'flip' and produce larger lines
small polygons that are counterclockwise and larger offsets may make the new polygon appear the wrong side of the original
very sharp vertices will produce even sharper offsets, as the calculated intersection point veers off to infinity
duplicated adjacent points might cause the routine to scratch its head and wonder how to draw a line parallel to them
Luxor.origin
— Methodorigin(pt:Point)
Reset the current position, scale, and orientation, then move the 0/0
position to pt
.
Luxor.origin
— Methodorigin()
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)
),
Luxor.paint
— Methodpaint()
Paint the current clip region with the current settings.
Luxor.pathlength
— Methodpathlength(path::Path;
steps=10)
Return the length of a Path.
The steps
parameter is used when approximating the length of any curve (Bezier) sections.
Luxor.pathsample
— Methodpathsample(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.
Luxor.pathtobezierpaths
— Methodpathtobezierpaths(
; 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
Luxor.pathtopoly
— Methodpathtopoly()
Convert the current path to an array of polygons.
Returns an array of polygons, corresponding to the paths and subpaths of the original path.
Luxor.perpendicular
— Methodperpendicular(p1, p2, k)
Return a point p3
that is k
units away from p1
, such that a line p1 p3
is perpendicular to p1 p2
.
Convention? to the right?
Luxor.perpendicular
— Methodperpendicular(p1::Point, p2::Point, p3::Point)
Return a point on a line passing through p1
and p2
that is perpendicular to p3
.
Luxor.perpendicular
— Methodperpendicular(p1, p2)
Return two points p3
and p4
such that a line from p3
to p4
is perpendicular to a line from p1
to p2
, the same length, and the lines intersect at their midpoints.
Luxor.perpendicular
— Methodperpendicular(p::Point)
Returns point Point(p.y, -p.x)
.
Luxor.pie
— Methodpie(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
Luxor.pie
— Methodpie(centerpoint::Point, radius::Real, startangle::Real, endangle::Real, action::Symbol)
Luxor.pie
— Methodpie(radius, startangle, endangle;
action=:none)
Make a pie shape centered at the origin, and add it to the current path.
Luxor.placeeps
— Methodplaceeps(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.
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 somehowignores 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"
Luxor.placeimage
— Functionplaceimage(svgimg, pos=O; centered=false)
Place an SVG image stored in svgimg
on the drawing at pos
. Use readsvg()
to read an SVG image from file, or from SVG code.
Use keyword centered=true
to place the center of the image at the position.
Luxor.placeimage
— Functionplaceimage(matrix::AbstractMatrix{UInt32}, pos=O;
alpha=1, centered=false)
Place an image matrix on the drawing at pos
with opacity/transparency alpha
.
Use keyword centered=true
to place the center of the image at the position.
Luxor.placeimage
— Methodplaceimage(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.
Luxor.placeimage
— Methodplaceimage(img, pt::Point=O, alpha; centered=false)
placeimage(pngimg, xpos, ypos, alpha; centered=false)
Place a PNG image pngimg
on the drawing at pt
or Point(xpos, ypos)
with opacity/transparency alpha
. The image has been previously loaded using readpng()
.
Use keyword centered=true
to place the center of the image at the position.
Luxor.placeimage
— Methodplaceimage(pngimg, pos=O; centered=false)
placeimage(pngimg, xpos, ypos; centered=false)
Place the PNG image on the drawing at pos
, or (xpos
/ypos
). The image img
has been previously read using readpng()
.
Use keyword centered=true
to place the center of the image at the position.
Luxor.pointcircletangent
— Methodpointcircletangent(point::Point, circlecenter::Point, circleradius)
Find the two points on a circle that lie on tangent lines passing through an external point.
If both points are O, the external point is inside the circle, and the result is (O, O)
.
Luxor.pointcrossesboundingbox
— Methodpointcrossesboundingbox(pt, bbox::BoundingBox)
Find and return the point where a line from the center of bounding box bbox
to point pt
would, if continued, cross the edges of the box.
Luxor.pointinverse
— Methodpointinverse(A::Point, centerpoint::Point, rad)
Find A′
, the inverse of a point A with respect to a circle centerpoint
/rad
, such that:
distance(centerpoint, A) * distance(centerpoint, A′) == rad^2
Return (true, A′) or (false, A).
Luxor.pointlinedistance
— Methodpointlinedistance(p::Point, a::Point, b::Point)
Find the distance between a point p
and a line between two points a
and b
.
Luxor.polar
— Methodpolar(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)
Luxor.poly
— Functionpoly(bbox::BoundingBox, :action; kwargs...)
Make a polygon around the BoundingBox in bbox
.
Luxor.poly
— MethodDraw 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.
Luxor.polyarea
— Methodpolyarea(p::Array)
Find the area of a simple polygon. It works only for polygons that don't self-intersect. See also polyorientation()
.
Luxor.polycentroid
— MethodFind 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()
.
Luxor.polyclip
— Methodpolyclip(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()
.
Luxor.polycross
— Functionpolycross(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)
Luxor.polydistances
— Methodpolydistances(p::Array{Point, 1}; closed=true)
Return an array of the cumulative lengths of a polygon.
Luxor.polyfit
— Functionpolyfit(plist::Array, npoints=30)
Build a polygon that constructs a B-spine approximation to it. The resulting list of points makes a smooth path that runs between the first and last points.
Luxor.polyhull
— Methodpolyhull(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.
Luxor.polyintersect
— Methodpolyintersect(p1::AbstractArray{Point, 1}, p2::AbstractArray{Point, 1};
closed=true)
TODO: Fix/test/improve this experimental polygon intersection routine.
Return the points where polygon p1 and polygon p2 cross.
If closed
is false, the intersection points must lie on the first n - 1
lines of each polygon.
Luxor.polymorph
— Methodpolymorph(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
Luxor.polymove!
— Methodpolymove!(pgon, frompoint::Point, topoint::Point)
Move (permanently) a polygon from frompoint
to topoint
.
Luxor.polyorientation
— Methodpolyorientation(pgon)
Returns a number which is positive if the polygon is clockwise in Luxor...
TODO This code is still experimental...
Luxor.polyperimeter
— Methodpolyperimeter(p::Array{Point, 1}; closed=true)
Find the total length of the sides of polygon p
.
Luxor.polyportion
— Functionpolyportion(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.
Luxor.polyreflect!
— Functionpolyreflect!(pgon, pt1 = O, pt2 = O + (0, 100)
Reflect (permanently) a polygon in a line (default to the y-axis) joining two points.
Luxor.polyremainder
— Functionpolyremainder(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.
Luxor.polyremovecollinearpoints
— Methodpolyremovecollinearpoints(pgon::Array{Point, 1})
Return copy of polygon with no collinear points.
Caution: may return an empty polygon... !
TODO This code is still experimental...
Luxor.polyrotate!
— Methodpolyrotate!(pgon, θ;
center=O)
Rotate (permanently) a polygon around center
by θ
radians.
Luxor.polysample
— Methodpolysample(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.
Luxor.polyscale!
— Methodpolyscale!(pgon, sh, sv;
center=O)
Scale (permanently) a polygon by sh
horizontally and sv
vertically, relative to center
.
Luxor.polyscale!
— Methodpolyscale!(pgon, s;
center=O)
Scale (permanently) a polygon by s
, relative to center
.
Luxor.polysmooth
— Methodpolysmooth(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.
Luxor.polysortbyangle
— FunctionSort 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))
Luxor.polysortbydistance
— MethodSort a polygon by finding the nearest point to the starting point, then the nearest point to that, and so on.
polysortbydistance(p, starting::Point)
You can end up with convex (self-intersecting) polygons, unfortunately.
Luxor.polysplit
— Methodpolysplit(p, p1, p2)
Split a polygon into two where it intersects with a line. It returns two polygons:
(poly1, poly2)
This doesn't always work, of course. For example, a polygon the shape of the letter "E" might end up being divided into more than two parts.
Luxor.polysuper
— Functionpolysuper(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
@@@@@@@@@@
Luxor.polytopath
— Methodpolytopath(ptlist)
Convert a polygon to a Path object.
@draw drawpath(polytopath(ngon(O, 145, 5, vertices = true)), action = :fill)
Luxor.polytriangulate
— Methodpolytriangulate(plist::Array{Point,1}; epsilon = -0.01)
Triangulate the polygon in plist
.
This uses the Bowyer–Watson/Delaunay algorithm to make triangles. It returns an array of triangular polygons.
TODO: This experimental polygon function is not very efficient, because it first copies the list of points (to avoid modifying the original), and sorts it, before making triangles.
Luxor.prettypoly
— Functionprettypoly(bbox::BoundingBox, action; kwargs...)
Make a decorated polygon around the BoundingBox in bbox
. The vertices are in the order: bottom left, top left, top right, and bottom right.
Luxor.prettypoly
— Functionprettypoly(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))))
Luxor.preview
— Methodpreview()
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
.
Luxor.randomcolor
— Methodrandomcolor()
Set a random color. This may change the current alpha opacity too.
Luxor.randomhue
— Methodrandomhue()
Set a random hue, without changing the current alpha opacity.
Luxor.randompoint
— Methodrandompoint(lowx, lowy, highx, highy)
Return a random point somewhere inside a rectangle defined by the four values.
Luxor.randompoint
— Methodrandompoint(lowpt, highpt)
Return a random point somewhere inside the rectangle defined by the two points.
Luxor.randompointarray
— Methodrandompointarray(lowx, lowy, highx, highy, n)
Return an array of n
random points somewhere inside the rectangle defined by the four coordinates.
Luxor.randompointarray
— Methodrandompointarray(w, h, d; attempts=20)
Return an array of randomly positioned points inside the rectangle defined by the current origin (0/0) and the width
and height
. d
determines the minimum distance between each point. Increase attempts
if you want the function to try harder to fill empty spaces; decrease it if it's taking too long to look for samples that work.
This uses Bridson's Poisson Disk Sampling algorithm: https://www.cs.ubc.ca/~rbridson/docs/bridson-siggraph07-poissondisk.pdf
Example
for pt in randompointarray(BoundingBox(), 20)
randomhue()
circle(pt, 10, :fill)
end
Luxor.randompointarray
— Methodrandompointarray(bbox::BoundingBox, d; attempts=20)
Return an array of randomly positioned points inside the bounding box d
units apart.
Luxor.randompointarray
— Methodrandompointarray(lowpt, highpt, n)
Return an array of n
random points somewhere inside the rectangle defined by two points.
Luxor.rawlatexboundingbox
— Methodrawlatexboundingbox(lstr::LaTeXString, font_size=1)
Helper function that returns the coordinate points of the bounding box containing the specific LaTeX text.
Luxor.readpng
— Methodreadpng(pathname)
Read a PNG file.
This returns a image object suitable for placing on the current drawing with placeimage()
. You can access its width
and height
fields:
image = readpng("test-image.png")
w = image.width
h = image.height
Luxor.readsvg
— Methodreadsvg(str)
Read an SVG image. str
is either pathname or pure SVG code. This returns an SVG image object suitable for placing on the current drawing with placeimage()
.
Placing an SVG file:
@draw begin
mycoollogo = readsvg("mylogo.svg")
placeimage(mycoollogo)
end
Placing SVG code:
# from https://github.com/edent/SuperTinyIcons
julialogocode = """<svg xmlns="http://www.w3.org/2000/svg"
aria-label="Julia" role="img"
viewBox="0 0 512 512">
<rect width="512" height="512" rx="15%" fill="#fff"/>
<circle fill="#389826" cx="256" cy="137" r="83"/>
<circle fill="#cb3c33" cx="145" cy="329" r="83"/>
<circle fill="#9558b2" cx="367" cy="329" r="83"/>
</svg>"""
@draw begin
julia_logo = readsvg(julialogocode)
placeimage(julia_logo, centered=true)
end
Luxor.rect
— Methodrect(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.
Luxor.rect
— Methodrect(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.
Luxor.rescale
— Functionrescale(x, from_min, from_max, to_min=0.0, to_max=1.0)
Convert x
from one linear scale (from_min
to from_max
) to another (to_min
to to_max
).
The scales can also be supplied in tuple form:
rescale(x, (from_min, from_max), (to_min, to_max))
using Luxor
julia> rescale(15, 0, 100, 0, 1)
0.15
julia> rescale(15, (0, 100), (0, 1))
0.15
julia> rescale(pi/20, 0, 2pi, 0, 1)
0.025
julia> rescale(pi/20, (0, 2pi), (0, 1))
0.025
julia> rescale(25, 0, 1, 0, 1.609344)
40.2336
julia> rescale(15, (0, 100), (1000, 0))
850.0
Luxor.rline
— Methodrline(pt)
Add a line relative to the current position to the pt
.
Luxor.rmove
— Methodrmove(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()
.
Luxor.rotate
— Methodrotate(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.
Luxor.rotatepoint
— Methodrotatepoint(targetpt::Point, angle)
Rotate a target point around the current origin by an angle specified in radians.
Returns the new point.
Luxor.rotatepoint
— Methodrotatepoint(targetpt::Point, originpt::Point, angle)
Rotate a target point around another point by an angle specified in radians.
Returns the new point.
Luxor.rotationmatrix
— Methodrotationmatrix(a)
Return a 3x3 Julia matrix that will apply a rotation through a
radians.
See getmatrix()
for details.
Luxor.rule
— Functionrule(pos, theta;
boundingbox=BoundingBox(),
vertices=false)
Draw a straight line through pos
at an angle theta
from the x axis.
By default, the line spans the entire drawing, but you can supply a BoundingBox to change the extent of the line.
rule(O) # draws an x axis
rule(O, pi/2) # draws a y axis
The function:
rule(O, pi/2, boundingbox=BoundingBox()/2)
draws a line that spans a bounding box half the width and height of the drawing, and returns a Set of end points. If you just want the vertices and don't want to draw anything, use vertices=true
.
Luxor.rulers
— Methodrulers()
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.
Luxor.scale
— Methodscale(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)
Luxor.scale
— Methodscale(f)
Scale the current workspace by f
in both x
and y
directions.
Values are relative to the current scale.
Luxor.scalingmatrix
— Methodscalingmatrix(sx, sy)
Return a 3x3 Julia matrix that will apply a scaling by sx
and sy
.
See getmatrix()
for details.
Luxor.sector
— Methodsector(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.
Luxor.sector
— Methodsector(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()
.
Luxor.sector
— Methodsector(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.
Luxor.sector
— Methodsector(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.
Luxor.setantialias
— Methodsetantialias(n)
Set the current antialiasing to a value between 0 and 6:
antialias_default = 0, the default antialiasing for the subsystem and target device
antialias_none = 1, use a bilevel alpha mask
antialias_gray = 2, use single-color antialiasing (using shades of gray for black text on a white background, for example)
antialias_subpixel = 3, take advantage of the order of subpixel elements on devices such as LCD panels
antialias_fast = 4, perform some antialiasing but prefer speed over quality
antialias_good = 5, balance quality against performance
antialias_best = 6, render at the highest quality, sacrificing speed if necessary
This affects subsequent graphics, but not text, and it doesn't apply to all types of output file.
Luxor.setbezierhandles
— Methodsetbezierhandles(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.
Luxor.setbezierhandles
— Methodsetbezierhandles(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.
Luxor.setblend
— Methodsetblend(blend)
Start using the named blend for filling graphics.
This aligns the original coordinates of the blend definition with the current axes.
Luxor.setblendextend
— Methodsetblendextend(blend::Blend, mode)
Specify how color blend patterns are repeated/extended. Supply the blend and one of the following strings:
"repeat": the pattern is repeated
"reflect": the pattern is reflected (repeated in reverse)
"pad": outside the pattern, use the closest color
"none": outside of the pattern, use transparent pixels
Luxor.setcolor
— Methodsetcolor("gold")
setcolor("darkturquoise")
Set the current color to a named color. This use the definitions in Colors.jl to convert a string to RGBA eg setcolor("gold")
or "green", "darkturquoise", "lavender", etc. The list is at Colors.color_names
.
Use sethue()
for changing colors without changing current opacity level.
sethue()
and setcolor()
return the three or four values that were used:
julia> setcolor(sethue("red")..., .8)
(1.0, 0.0, 0.0, 0.8)
julia> sethue(setcolor("red")[1:3]...)
(1.0, 0.0, 0.0)
You can also do:
using Colors
sethue(colorant"red")
See also setcolor
.
Luxor.setcolor
— Methodsetcolor(r, g, b)
setcolor(r, g, b, alpha)
setcolor(color)
setcolor(col::Colors.Colorant)
setcolor(sethue("red")..., .2)
Set the current color.
Examples:
setcolor(convert(Colors.HSV, Colors.RGB(0.5, 1, 1)))
setcolor(.2, .3, .4, .5)
setcolor(convert(Colors.HSV, Colors.RGB(0.5, 1, 1)))
for i in 1:15:360
setcolor(convert(Colors.RGB, Colors.HSV(i, 1, 1)))
...
end
See also sethue
.
Luxor.setcolor
— Methodsetcolor((r, g, b, a))
Set the color to the tuple's values.
Luxor.setcolor
— Methodsetcolor((r, g, b))
Set the color to the tuple's values.
Luxor.setdash
— Functionsetdash(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.
Luxor.setdash
— Methodsetdash("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.
Luxor.setfillrule
— Functionsetfillrule(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
.
Luxor.setfont
— Methodsetfont(family, fontsize)
Select a font and specify the size.
Example:
setfont("Helvetica", 24)
settext("Hello in Helvetica 24 using the Pro API", Point(0, 10))
Luxor.setgray
— Methodsetgray(n)
setgrey(n)
Set the color to a gray level of n
, where n
is between 0 and 1.
Luxor.sethue
— Methodsethue("black")
sethue(0.3, 0.7, 0.9)
setcolor(sethue("red")..., .2)
Set the color without changing opacity.
sethue()
is like setcolor()
, but we sometimes want to change the current color without changing alpha/opacity. Using sethue()
rather than setcolor()
doesn't change the current alpha opacity.
See also setcolor
.
Luxor.sethue
— Methodsethue(0.3, 0.7, 0.9)
Set the color's r
, g
, b
values. Use setcolor(r, g, b, a)
to set transparent colors.
Luxor.sethue
— Methodsethue(col::Colors.Colorant)
Set the color without changing the current alpha/opacity:
Luxor.sethue
— Methodsethue((r, g, b, a))
Set the color to the tuple's values.
Luxor.sethue
— Methodsethue((r, g, b))
Set the color to the tuple's values.
Luxor.setline
— Methodsetline(n)
Set the line width, in points.
Use getline()
to get the current value.
Luxor.setlinecap
— Functionsetlinecap(s)
Set the line ends. s
can be "butt" or :butt
(the default), "square" or :square
, or "round" or :round
.
Luxor.setlinejoin
— Functionsetlinejoin("miter")
setlinejoin("round")
setlinejoin("bevel")
Set the way line segments are joined when the path is stroked.
The default joining style is "mitered".
Luxor.setmatrix
— Methodsetmatrix(m::Array)
Change the current matrix to 6-element matrix m
.
See getmatrix()
for details.
Luxor.setmesh
— Methodsetmesh(mesh::Mesh)
Select a mesh, previously created with mesh()
, for filling and stroking subsequent graphics.
Luxor.setmode
— Methodsetmode(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.
Luxor.setopacity
— Methodsetopacity(alpha)
Set the current opacity to a value between 0 and 1. This modifies the alpha value of the current color.
Luxor.setstrokescale
— Methodsetstrokescale(state::Bool)
Enable/disable stroke scaling for the current drawing.
Luxor.setstrokescale
— Methodsetstrokescale()
Return the current stroke scaling setting.
Luxor.settext
— Methodsettext(text, pos;
halign = "left",
valign = "bottom",
angle = 0, # degrees!
markup = false)
settext(text;
kwargs)
Draw the text
at pos
(if omitted defaults to 0/0
). If no font is specified, on macOS the default font is Times Roman.
To align the text, use halign
, one of "left", "center", or "right", and valign
, one of "top", "center", or "bottom".
angle
is the rotation - in counterclockwise degrees, rather than Luxor's default clockwise (+x-axis to +y-axis) radians.
If markup
is true
, then the string can contain some HTML-style markup. Supported tags include:
<b>, <i>, <s>, <sub>, <sup>, <small>, <big>, <u>, <tt>, and <span>
The <span>
tag can contains things like this:
<span font='26' background='green' foreground='red'>unreadable text</span>
Luxor.shiftbezierhandles
— Methodshiftbezierhandles(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.
Luxor.simplify
— FunctionSimplify a polygon:
simplify(pointlist::Array, detail=0.1)
detail
is the maximum approximation error of simplified polygon.
Luxor.slope
— Methodslope(pointA::Point, pointB::Point)
Find angle of a line starting at pointA
and ending at pointB
.
Return a value between 0 and 2pi. Value will be relative to the current axes.
slope(O, Point(0, 100)) |> rad2deg # y is positive down the page
90.0
slope(Point(0, 100), O) |> rad2deg
270.0
The slope isn't the same as the gradient. A vertical line going up has a slope of 3π/2.
Luxor.snapshot
— Methodsnapshot(;
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.
Luxor.spiral
— Methodspiral(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.
Luxor.splitbezier
— Methodsplitbezier(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
Luxor.splittext
— Methodsplittext(s)
Split the text in string s
into an array, but keep all the separators attached to the preceding word.
Luxor.squircle
— Methodsquircle(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()
.
Luxor.star
— Functionstar(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)
Luxor.storepath
— Methodstorepath()
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()
.
Luxor.strokepath
— Methodstrokepath()
Stroke the current path with the current line width, line join, line cap, dash, and stroke scaling settings. The current path is then emptied.
Luxor.strokepreserve
— Methodstrokepreserve()
Stroke the current path with current line width, line join, line cap, dash, and stroke scaling settings, but then keep the path current.
Luxor.svgstring
— Methodsvgstring()
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`)
Luxor.texalign
— Methodtexalign(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.
Luxor.text
— Methodtext(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
Luxor.text
— Methodtext(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
Luxor.textbox
— Functiontextbox(s::T where T <: AbstractString, pos::Point=O;
leading = 12,
linefunc::Function = (linenumber, linetext, startpos, height) -> (),
alignment=:left)
Luxor.textbox
— Functiontextbox(lines::Array, pos::Point=O;
leading = 12,
linefunc::Function = (linenumber, linetext, startpos, height) -> (),
alignment=:left)
Draw the strings in the array lines
vertically downwards. leading
controls the spacing between each line (default 12), and alignment
determines the horizontal alignment (default :left
).
Optionally, before each line, execute the function linefunc(linenumber, linetext, startpos, height)
.
Returns the position of what would have been the next line.
See also textwrap()
, which modifies the text so that the lines fit into a specified width.
Luxor.textcurve
— Functiontextcurve(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
.
Luxor.textcurvecentered
— Methodtextcurvecentered(the_text, the_angle, the_radius, center::Point;
clockwise = true,
letter_spacing = 0,
baselineshift = 0
This version of the textcurve()
function is designed for shorter text strings that need positioning around a circle. (A cheesy effect much beloved of hipster brands and retronauts.)
letter_spacing
adjusts the tracking/space between chars, tighter is (-), looser is (+)). baselineshift
moves the text up or down away from the baseline.
textcurvecentred
(UK spelling) is a synonym.
Luxor.textextents
— Methodtextextents(str)
Return an array of six Float64s containing the measurements of the string str
when set using the current font settings (Toy API):
1 x_bearing
2 y_bearing
3 width
4 height
5 x_advance
6 y_advance
The x and y bearings are the displacement from the reference point to the upper-left corner of the bounding box. It is often zero or a small positive value for x displacement, but can be negative x for characters like "j"; it's almost always a negative value for y displacement.
The width and height then describe the size of the bounding box. The advance takes you to the suggested reference point for the next letter. Note that bounding boxes for subsequent blocks of text can overlap if the bearing is negative, or the advance is smaller than the width would suggest.
Example:
textextents("R")
returns
[1.18652; -9.68335; 8.04199; 9.68335; 9.74927; 0.0]
Luxor.textfit
— Functiontextfit(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)
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!
Luxor.textlines
— Methodtextlines(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?
Luxor.textonpoly
— Methodtextonpoly(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.
Luxor.textoutlines
— Methodtextoutlines(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.
Luxor.textpath
— Methodtextpath(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.
Luxor.textpath
— Methodtextpath(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.
Luxor.textplace
— Methodtextplace(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" # stickysize
is fontsize # pts # stickycolor
is color # stickykern
amount (pixels) shifted to the right # resets after each charshift
= baseline shifted vertically # resets after each charadvance
- 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,),
])
Luxor.texttrack
— Methodtexttrack(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 "̈"?
Luxor.textwrap
— Methodtextwrap(s::T where T<:AbstractString, width::Real, pos::Point;
rightgutter=5,
leading=0)
textwrap(s::T where T<:AbstractString, width::Real, pos::Point, linefunc::Function;
rightgutter=5,
leading=0)
Draw the string in s
by splitting it at whitespace characters into lines, so that each line is no longer than width
units. The text starts at pos
such that the first line of text is drawn entirely below a line drawn horizontally through that position. Each line is aligned on the left side, below pos
.
See also textbox()
.
Optionally, before each line, execute the function linefunc(linenumber, linetext, startpos, leading)
.
If you don't supply a value for leading
, the font's built-in extents are used.
Text with no whitespace characters won't wrap. You can write a simple chunking function to split a string or array into chunks:
chunk(x, n) = [x[i:min(i+n-1,length(x))] for i in 1:n:length(x)]
For example:
textwrap(the_text, 300, boxtopleft(BoundingBox()) + 20,
(ln, lt, sp, ht) -> begin
c = count(t -> occursin(r"[[:punct:]]", t), split(lt, ""))
@layer begin
fontface("Menlo")
sethue("darkred")
text(string("[", c, "]"), sp + (310, 0))
end
end)
puts a count of the number of punctuation characters in each line at the end of the line.
Returns the position of what would have been the next line.
Luxor.tickline
— Methodtickline(startpos, finishpos;
startnumber = 0,
finishnumber = 1,
major = 1,
minor = 0,
major_tick_function = nothing,
minor_tick_function = nothing,
rounding = 2,
axis = true, # draw the line?
log = false,
vertices = false # just return the points
)
Draw a line with ticks. major
is the number of ticks required between the start and finish point. So 1
divides the line in half. minor
is the number of ticks between each major tick.
Examples
tickline(Point(0, 0), Point(100, 0))
tickline(Point(0, 0), Point(100, 0), major = 4)
majorticks, minorticks = tickline(Point(0, 0), Point(100, 0), axis=false)
Custom ticks
Supply functions to make custom ticks. Custom tick functions should have arguments as follows:
function mtick(n, pos;
startnumber = 0,
finishnumber = 1,
nticks = 1)
...
and
function mntick(n, pos;
startnumber = 0,
finishnumber = 1,
nticks = 1,
majorticklocations = [])
...
For example:
tickline(O - (300, 0), Point(300, 0),
startnumber = -10,
finishnumber = 10,
minor = 0,
major = 4,
axis = false,
major_tick_function = (n, pos;
startnumber=30, finishnumber=40, nticks=10) -> begin
@layer begin
translate(pos)
ticklength = get_fontsize()
line(O, O + polar(ticklength, 3π/2), :stroke)
k = rescale(n, 0, nticks - 1, startnumber, finishnumber)
ticklength = get_fontsize() * 1.3
text("$(round(k, digits=2))",
O + (0, ticklength),
halign=:center,
valign=:middle,
angle = -getrotation())
end
end)
Luxor.tidysvg
— Methodtidysvg(fromfile, tofile)
Read the SVG image in fromfile
and write it to tofile
with modified glyph names.
Luxor.tidysvg
— Methodtidysvg(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.
Luxor.transform
— Methodtransform(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.
Luxor.translate
— Methodtranslate(point)
translate(x::Real, y::Real)
Translate the current workspace to x
and y
or to pt
.
Values are relative to the current location.
Luxor.translationmatrix
— Methodtranslationmatrix(x, y)
Return a 3x3 Julia matrix that will apply a translation in x
and y
.
See getmatrix()
for details.
Luxor.trianglecenter
— Methodtrianglecenter(pt1::Point, pt2::Point, pt3::Point)
Return the centroid of the triangle defined by pt1
, pt2
, and pt3
.
Luxor.trianglecircumcenter
— Methodtrianglecircumcenter(pt1::Point, pt2::Point, pt3::Point)
Return the circumcenter of the triangle defined by pt1
, pt2
, and pt3
. The circumcenter is the center of a circle that passes through the vertices of the triangle.
Luxor.triangleincenter
— Methodtriangleincenter(pt1::Point, pt2::Point, pt3::Point)
Return the incenter of the triangle defined by pt1
, pt2
, and pt3
. The incenter is the center of a circle inscribed inside the triangle.
Luxor.triangleorthocenter
— Methodtriangleorthocenter(pt1::Point, pt2::Point, pt3::Point)
Return the orthocenter of the triangle defined by pt1
, pt2
, and pt3
.
Luxor.trimbezier
— Methodtrimbezier(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.
Luxor.writelatexchar
— Methodwritelatexchar(t::AbstractString)
Helper function to handle extra chars that are not supported in MathTeXEngine.
Luxor.AnimatedGif
— TypeWraps the location of an animated gif so that it can be displayed
Luxor.BezierPath
— TypeBezierPath is an array of BezierPathSegments. segments
is Vector{BezierPathSegment}
.
Luxor.BezierPathSegment
— TypeBezierPathSegment is an array of four points:
p1
- start point cp1
- control point for start point cp2
- control point for finishpoint p2
- finish point
Luxor.BoundingBox
— MethodBoundingBox(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
).
Luxor.BoundingBox
— MethodBoundingBox(tile::BoxmapTile)
Return a BoundingBox of a BoxmapTile (as created with boxmap()
).
Luxor.BoundingBox
— MethodBoundingBox(path::Path)
Find bounding box of a stored Path (made with storepath()
).
Luxor.BoundingBox
— MethodBoundingBox(t::table, rownumber, columnnumber)
Return a BoundingBox that encloses cell at row rownumber
, column colnumber
of table t
.
Luxor.BoundingBox
— MethodBoundingBox(t::table, cell)
Return a BoundingBox that encloses cell cell
of table t
.
Luxor.BoundingBox
— MethodBoundingBox(t::table)
Return a BoundingBox that encloses the table t
.
Luxor.BoundingBox
— MethodBoundingBox(t::Tiler, r, c)
Return the Bounding Box enclosing the tile at row r
column c
.
Luxor.BoundingBox
— MethodBoundingBox(t::Tiler, n)
Return the Bounding Box enclosing tile n
.
Luxor.BoundingBox
— MethodBoundingBox(pointlist::Array)
Return the BoundingBox of a polygon (array of points).
Luxor.BoundingBox
— MethodBoundingBox()
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.
Luxor.Drawing
— TypeCreate 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)
Luxor.GridHex
— TypeGridHex(startpoint, radius, width=1200.0, height=1200.0)
Define a hexagonal grid, to start at startpoint
and proceed along the x-axis and then along the y-axis, radius
is the radius of a circle that encloses each hexagon. The distance in x
between the centers of successive hexagons is:
$\frac{\sqrt{(3)} radius}{2}$
To get the next point from the grid, use nextgridpoint(g::Grid)
.
When you run out of grid points, you'll wrap round and start again.
Luxor.GridRect
— TypeGridRect(startpoint, xspacing, yspacing, width, height)
Define a rectangular grid, to start at startpoint
and proceed along the x-axis in steps of xspacing
, then along the y-axis in steps of yspacing
.
GridRect(startpoint, xspacing=100.0, yspacing=100.0, width=1200.0, height=1200.0)
For a column, set the xspacing
to 0:
grid = GridRect(O, 0, 40)
To get points from the grid, use nextgridpoint(g::Grid)
.
julia> grid = GridRect(O, 0, 40);
julia> nextgridpoint(grid)
Luxor.Point(0.0, 0.0)
julia> nextgridpoint(grid)
Luxor.Point(0.0, 40.0)
When you run out of grid points, you'll wrap round and start again.
Luxor.Hexagon
— TypeHexagon
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
Luxor.HexagonAxial
— TypeHexagonAxial
Two axes
q:: first index
r:: second index
origin::Point
width:: of tile
height:: of tile
Luxor.HexagonCubic
— TypeHexagonCubic
Three axes
q:: first index
r:: second index
s:: third index
origin::Point
width:: of tile
height:: of tile
Luxor.HexagonOffsetEvenR
— TypeHexagonOffsetEvenR
even rows shifted right
Luxor.HexagonOffsetOddR
— TypeHexagonOffsetOddR
odd rows shifted right
Luxor.Movie
— TypeThe Movie
and Scene
types and the animate()
function are designed to help you create the frames that can be used to make an animated GIF or movie.
1 Provide width, height, title, and optionally a frame range to the Movie constructor:
demo = Movie(400, 400, "test", 1:500)
2 Define one or more scenes and scene-drawing functions.
3 Run the animate()
function, calling those scenes.
Example
bang = Movie(400, 100, "bang")
backdrop(scene, framenumber) = background("black")
function frame1(scene, framenumber)
background("white")
sethue("black")
eased_n = scene.easingfunction(framenumber, 0, 1, scene.framerange.stop)
circle(O, 40 * eased_n, :fill)
end
animate(bang, [
Scene(bang, backdrop, 0:200),
Scene(bang, frame1, 0:200, easingfunction=easeinsine)],
creategif=true,
pathname="/tmp/animationtest.gif")
Luxor.Movie
— MethodMovie(width, height, movietitle)
Define a movie, specifying the width, height, and a title. The title will be used to make the output file name. The range defaults to 1:250
.
Luxor.Partition
— Typep = Partition(areawidth, areaheight, tilewidth, tileheight)
A Partition is an iterator that, for each iteration, returns a tuple of:
the
x
/y
point of the center of each tile in a set of tiles that divide up a rectangular space such as a page into rows and columns (relative to current 0/0)the number of the tile
areawidth
and areaheight
are the dimensions of the area to be tiled, tilewidth
/tileheight
are the dimensions of the tiles.
Tiler and Partition are similar:
Partition lets you specify the width and height of a cell
Tiler lets you specify how many rows and columns of cells you want, and a margin
tiles = Partition(1200, 1200, 30, 30) for (pos, n) in tiles
the point pos is the center of the tile
end
You can access the calculated tile width and height like this:
tiles = Partition(1200, 1200, 30, 30)
for (pos, n) in tiles
ellipse(pos.x, pos.y, tiles.tilewidth, tiles.tileheight, :fill)
end
It's sometimes useful to know which row and column you're currently on:
tiles.currentrow
tiles.currentcol
should have that information for you.
Unless the tilewidth and tileheight are exact multiples of the area width and height, you'll see a border at the right and bottom where the tiles won't fit.
Luxor.Path
— TypeA Path object contains, in the .path
field, a vector of PathElement
s (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()
])
Luxor.Path
— MethodPath(ptlist::Vector{Point}; close=false))
Create a Path from the points in ptlist
.
Luxor.Point
— TypeThe Point type holds two coordinates. It's immutable, you can't change the values of the x and y values directly.
Luxor.Scene
— TypeThe Scene type defines a function to be used to render a range of frames in a movie.
- the
movie
created by Movie() - the
framefunction
is a function taking two arguments: the scene and the framenumber. - the
framerange
determines which frames are processed by the function. Defaults to the entire movie. - the optional
easingfunction
can be accessed by the framefunction to vary the transition speed - the optional
opts
which is a single argument of an abstract type which can be accessed within the framefunction
Luxor.Scene
— MethodScene(movie, function, range;
easingfunction=easinoutquad,
optarg=nothing)
Use the Scene() constructor function to create a scene. Supply a movie, a function to generate the scene, and a range of frames. Optionally you can supply an easing function, and other information, in optarg
, which can be accessed as scene.opts
.
Example
function initial(scene, framenumber)
balls = scene.opts
...
end
animate(poolmovie, [
Scene(poolmovie, initial, optarg=balls, 1:20),
...
])
To use an easing function inside the frame-generating function, you can create a normalized value with, for example:
eased_n = scene.easingfunction(framenumber, 0, 1, scene.framerange.stop)
Or, if the scene doesn't start at frame 1, calculate normalized easing function like this:
eased_n = scene.easingfunction(framenumber - scene.framerange.start,
0, 1, scene.framerange.stop - scene.framerange.start)
Luxor.Table
— Typet = Table(nrows, ncols)
t = Table(nrows, ncols, colwidth, rowheight)
t = Table(rowheights, columnwidths)
Tables are centered at O
, but you can supply a point after the specifications.
t = Table(nrows, ncols, centerpoint)
t = Table(nrows, ncols, colwidth, rowheight, centerpoint)
t = Table(rowheights, columnwidths, centerpoint)
Examples
Simple tables
t = Table(4, 3) # 4 rows and 3 cols, default is 100w, 50 h
t = Table(4, 3, 80, 30) # 4 rows of 30pts high, 3 cols of 80pts wide
t = Table(4, 3, (80, 30)) # same
t = Table((4, 3), (80, 30)) # same
Specify row heights and column widths instead of quantities:
t = Table([60, 40, 100], 50) # 3 different height rows, 1 column 50 wide
t = Table([60, 40, 100], [100, 60, 40]) # 3 rows, 3 columns
t = Table(fill(30, (10)), [50, 50, 50]) # 10 rows 30 high, 3 columns 10 wide
t = Table(50, [60, 60, 60]) # just 1 row (50 high), 3 columns 60 wide
t = Table([50], [50]) # just 1 row, 1 column, both 50 units wide
t = Table(50, 50, 10, 5) # 50 rows, 50 columns, 10 units wide, 5 units high
t = Table([6, 11, 16, 21, 26, 31, 36, 41, 46], [6, 11, 16, 21, 26, 31, 36, 41, 46])
t = Table(15:5:55, vcat(5:2:15, 15:-2:5))
# table has 108 cells, with:
# row heights: 15 20 25 30 35 40 45 50 55
# col widths: 5 7 9 11 13 15 15 13 11 9 7 5
t = Table(vcat(5:10:60, 60:-10:5), vcat(5:10:60, 60:-10:5))
t = Table(vcat(5:10:60, 60:-10:5), 50) # 1 column 50 units wide
t = Table(vcat(5:10:60, 60:-10:5), 1:5:50)
A Table is an iterator that, for each iteration, returns a tuple of:
the
x
/y
point of the center of cells arranged in rows and columns (relative to current 0/0)the number of the cell (left to right, then top to bottom)
nrows
/ncols
are the number of rows and columns required.
It's sometimes useful to know which row and column you're currently on while iterating:
t.currentrow
t.currentcol
and row heights and column widths are available in:
t.rowheights
t.colwidths
box(t::Table, r, c)
can be used to fill table cells:
@svg begin
for (pt, n) in (t = Table(8, 3, 30, 15))
randomhue()
box(t, t.currentrow, t.currentcol, :fill)
sethue("white")
text(string(n), pt)
end
end
or without iteration, using cellnumber:
@svg begin
t = Table(8, 3, 30, 15)
for n in eachindex(t)
randomhue()
box(t, n, :fill)
sethue("white")
text(string(n), t[n])
end
end
To use a Table to make grid points:
julia> first.(collect(Table(10, 6)))
60-element Array{Luxor.Point,1}:
Luxor.Point(-10.0, -18.0)
Luxor.Point(-6.0, -18.0)
Luxor.Point(-2.0, -18.0)
⋮
Luxor.Point(2.0, 18.0)
Luxor.Point(6.0, 18.0)
Luxor.Point(10.0, 18.0)
which returns an array of points that are the center points of the cells in the table.
Luxor.Tiler
— Typetiles = Tiler(areawidth, areaheight, nrows, ncols, margin=20)
A Tiler is an iterator that, for each iteration, returns a tuple of:
the
x
/y
point of the center of each tile in a set of tiles that divide up a rectangular space such as a page into rows and columns (relative to current 0/0)the number of the tile
areawidth
and areaheight
are the dimensions of the area to be tiled, nrows
/ncols
are the number of rows and columns required, and margin
is applied to all four edges of the area before the function calculates the tile sizes required.
Tiler and Partition are similar:
Partition lets you specify the width and height of a cell
Tiler lets you specify how many rows and columns of cells you want, and a margin:
tiles = Tiler(1000, 800, 4, 5, margin=20)
for (pos, n) in tiles
# the point pos is the center of the tile
end
You can access the calculated tile width and height like this:
tiles = Tiler(1000, 800, 4, 5, margin=20)
for (pos, n) in tiles
ellipse(pos.x, pos.y, tiles.tilewidth, tiles.tileheight, :fill)
end
It's sometimes useful to know which row and column you're currently on. tiles.currentrow
and tiles.currentcol
should have that information for you.
To use a Tiler to make grid points:
first.(collect(Tiler(800, 800, 4, 4)))
which returns an array of points that are the center points of the grid.
Luxor.Turtle
— TypeTurtle()
Turtle(O)
Turtle(0, 0)
Turtle(O, pendown=true, orientation=0, pencolor=(1.0, 0.25, 0.25))
Create a Turtle. You can command a turtle to move and draw "turtle graphics".
The commands (unusually for Julia) start with a capital letter, and angles are specified in degrees.
Basic commands are Forward()
, Turn()
, Pendown()
, Penup()
, Pencolor()
, Penwidth()
, Circle()
, Orientation()
, Rectangle()
, and Reposition()
.
Others include Push()
, Pop()
, Message()
, HueShift()
, Randomize_saturation()
, Reposition()
, and Pen_opacity_random()
.
Luxor.O
— ConstantO is a shortcut for the current origin, 0/0
Luxor.paper_sizes
— Constantpaper_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))