Simple graphics

Simple graphics

In Luxor, there are different ways of working with graphical items. Some, such as lines, rectangles and circles, are drawn immediately (ie placed on the drawing and then "forgotten"). Others can be constructed and then converted to lists of points for further processing. For these, watch out for a vertices=true option.

Rectangles and boxes

rects

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

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

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

source
rect(cornerpoint, w, h, action)

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

source
Luxor.boxFunction.
box(cornerpoint1, cornerpoint2, action=:nothing)

Create a rectangle between two points and do an action.

source
box(points::Array, action=:nothing)

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

source
box(pt::Point, width, height, action=:nothing; vertices=false)

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

source
box(x, y, width, height, action=:nothing)

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

source
box(x, y, width, height, cornerradius, action=:nothing)

Create a box/rectangle centered at point x/y with width and height. Round each corner by cornerradius.

source
Luxor.polybboxFunction.

Find the bounding box of a polygon (array of points).

polybbox(pointlist::Array)

Return the two opposite corners (suitable for box(), for example).

source

For regular polygons, see the next section on Polygons.

Circles and ellipses

There are various ways to make circles, including by center and radius, or passing through two points:

p1 = O
p2 = Point(100, 0)
sethue("red")
circle(p1, 40, :fill)
sethue("green")
circle(p1, p2, :stroke)
sethue("black")
arrow(O, Point(0, -40))
map(p -> circle(p, 4, :fill), [p1, p2])

circles

Or passing through three points. The center3pts() function returns the center position and radius of a circle passing through three points:

sethue("black")
p1 = Point(0, -50)
p2 = Point(100, 0)
p3 = Point(0, 65)
map(p -> circle(p, 4, :fill), [p1, p2, p3])
circle(center3pts(p1, p2, p3)..., :stroke)

center and radius of 3 points

With ellipse() you can place ellipses (and circles) by defining the center point and the width and height.

tiles = Tiler(500, 300, 5, 5)
width = 20
height = 25
for (pos, n) in tiles
    randomhue()
    ellipse(pos, width, height, :fill)
    sethue("black")
    label = string(round(width/height, 2))
    textcentered(label, pos.x, pos.y + 25)
    width += 2
end

ellipses

It's also possible to construct polygons that are approximations to ellipses with two focal points and a distance.

f1 = Point(-100, 0)
f2 = Point(100, 0)
ellipsepoly = ellipse(f1, f2, 170, :none, vertices=true)
[ begin
    setgray(rescale(c, 150, 1, 0, 1))
    poly(offsetpoly(ellipsepoly, c), close=true, :fill);
    rotate(pi/20)
  end
     for c in 150:-10:1 ]

more ellipses

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

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

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

source
circle(pt, r, action)

Make a circle centered at pt.

source
circle(pt1::Point, pt2::Point, action=:nothing)

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

source
Luxor.ellipseFunction.

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

ellipse(xc, yc, w, h, action=:none)
source

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

ellipse(cpt, w, h, action=:none)
source
ellipse(focus1::Point, focus2::Point, k, action=:none;
        stepvalue=pi/100,
        vertices=false,
        reversepath=false)

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

source

circlepath() constructs a circular path from Bèzier curves, which allows you to use circles as paths.

setline(4)
tiles = Tiler(600, 250, 1, 5)
for (pos, n) in tiles
    randomhue()
    circlepath(pos, tiles.tilewidth/2, :path)
    newsubpath()
    circlepath(pos, rand(5:tiles.tilewidth/2 - 1), :fill, reversepath=true)
end

circles as paths

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

Draw a circle using Bézier curves.

source

More curved shapes: sectors, spirals, and squircles

A sector (technically an "annular sector") has an inner and outer radius, as well as start and end angles.

sethue("tomato")
sector(50, 90, pi/2, 0, :fill)
sethue("olive")
sector(Point(O.x + 200, O.y), 50, 90, 0, pi/2, :fill)

sector

You can also supply a value for a corner radius. The same sector is drawn but with rounded corners.

sethue("tomato")
sector(50, 90, pi/2, 0, 15, :fill)
sethue("olive")
sector(Point(O.x + 200, O.y), 50, 90, 0, pi/2, 15, :fill)

sector

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

Draw an annular sector centered at centerpoint.

source

Draw an annular sector centered at the origin.

source
sector(centerpoint::Point, innerradius, outerradius, startangle, endangle, cornerradius, action:none)

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

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

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

source

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

source

A pie (or wedge) has start and end angles.

pie(0, 0, 100, pi/2, pi, :fill)

pie

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

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

source
pie(centerpoint, radius, startangle, endangle, action=:none)

Draw a pie shape centered at centerpoint.

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

source

Draw a pie shape centered at the origin

source

To construct spirals, use the spiral() function. These can be drawn directly, or used as polygons. The default is to draw Archimedes (non-logarithmic) spirals.

sp = spiral(4, 1, stepby=pi/24, period=12pi, vertices=true)

for i in 1:10
    setgray(i/10)
    setline(22-2i)
    poly(sp, :stroke)
end

spiral

Use the log=true option to draw logarithmic spirals.

sp = spiral(2, .12, log=true, stepby=pi/24, period=12pi, vertices=true)

for i in 1:10
    setgray(i/10)
    setline(22-2i)
    poly(sp, :stroke)
end

spiral log

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

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

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

lituus: -2

hyperbolic spiral: -1

Archimedes' spiral: 1

Fermat's spiral: 2

For logarithmic spirals (log=true):

golden spiral: b = ln(phi)/ (pi/2) (about 0.30)

Values of b around 0.1 produce tighter, staircase-like spirals.

source

A squircle is a cross between a square and a circle. You can adjust the squariness and circularity of it to taste by supplying a value for keyword rt:

setline(2)
tiles = Tiler(600, 250, 1, 3)
for (pos, n) in tiles
    sethue("lavender")
    squircle(pos, 80, 80, rt=[0.3, 0.5, 0.7][n], :fillpreserve)
    sethue("grey20")
    strokepath()
    textcentered("rt = $([0.3, 0.5, 0.7][n])", pos)
end

squircles

Luxor.squircleFunction.

Make a squircle (basically a rectangle with rounded corners). Specify the center position, horizontal radius (distance from center to a side), and vertical radius (distance from center to top or bottom):

squircle(center::Point, hradius, vradius, action=:none; rt = 0.5, vertices=false)

The rt option defaults to 0.5, and gives an intermediate shape. Values less than 0.5 make the shape more square. Values above make the shape more round.

source

To draw a simple rounded rectangle, supply a corner radius:

setline(4)
box(O, 200, 150, 10, :stroke)

rounded rect 1

Or you could smooth the corners of a box, like so:

setline(4)
polysmooth(box(O, 200, 150, vertices=true), 10, :stroke)

rounded rect

Paths and positions

A path is a sequence of lines and curves. You can add lines and curves to the current path, then use closepath() to join the last point to the first.

A path can have subpaths, created withnewsubpath(), which can form holes.

There is a 'current position' which you can set with move(), and can use implicitly in functions like line(), text(), arc() and curve().

Luxor.moveFunction.
move(x, y)
move(pt)

Move to a point.

source
Luxor.rmoveFunction.
rmove(x, y)

Move by an amount from the current point. Move relative to current position by x and y:

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

rmove(pt)
source
Luxor.newpathFunction.
newpath()

Create a new path. This is Cairo's new_path() function.

source
Luxor.newsubpathFunction.
newsubpath()

Add a new subpath to the current path. This is Cairo's new_sub_path() function. It can be used for example to make holes in shapes.

source
Luxor.closepathFunction.
closepath()

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

source

Lines

Use line() and rline() to draw straight lines.

Luxor.lineFunction.
line(x, y)
line(x, y, :action)
line(pt)

Create a line from the current position to the x/y position and optionally apply an action:

source
line(pt1::Point, pt2::Point, action=:nothing)

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

source
Luxor.rlineFunction.
rline(x, y)
rline(x, y, :action)
rline(pt)

Create a line relative to the current position to the x/y position and optionally apply an action:

source

You can use rule() to draw a line across the entire drawing through a point, at an angle to the current x-axis.

y = 10
for x in logspace(0, 2.75, 40)
    circle(Point(x, y), 2, :fill)
    rule(Point(x, y), -pi/2)
    y += 2
end

arc

Luxor.ruleFunction.
rule(pos::Point, theta=0.0)

Draw a line across the entire drawing passing through pos, at an angle of theta to the x-axis. Returns the two points.

The end points are not calculated exactly, they're just a long way apart.

source

Arcs and curves

There are a few standard arc-drawing commands, such as curve(), arc(), carc(), and arc2r().

curve() constructs Bèzier curves from control points:

setline(.5)
pt1 = Point(0, -125)
pt2 = Point(200, 125)
pt3 = Point(200, -125)

sethue("red")
map(p -> circle(p, 4, :fill), [O, pt1, pt2, pt3])

line(O, pt1, :stroke)
line(pt2, pt3, :stroke)

sethue("black")
setline(3)

move(O)
curve(pt1, pt2, pt3)
strokepath()

curve

arc2r() draws a circular arc that joins two points:

tiles = Tiler(700, 200, 1, 6)
for (pos, n) in tiles
    c1, pt2, pt3 = ngon(pos, rand(10:50), 3, rand(0:pi/12:2pi), vertices=true)
    sethue("black")
    map(pt -> circle(pt, 4, :fill), [c1, pt3])
    sethue("red")
    circle(pt2, 4, :fill)
    randomhue()
    arc2r(c1, pt2, pt3, :stroke)
end

arc

Luxor.arcFunction.

Add an arc to the current path from angle1 to angle2 going clockwise.

arc(xc, yc, radius, angle1, angle2, action=:nothing)

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

source

Arc with centerpoint.

arc(centerpoint::Point, radius, angle1, angle2, action=:nothing)
source
Luxor.arc2rFunction.
  arc2r(c1::Point, p2::Point, p3::Point, action=:nothing)

Make a circular arc centered at c1 that starts at p2 and ends at p3, going clockwise.

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

source
Luxor.carcFunction.

Add an arc to the current path from angle1 to angle2 going counterclockwise.

carc(xc, yc, radius, angle1, angle2, action=:nothing)

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

source

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

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

Make a circular arc centered at c1 that starts at p2 and ends at p3, going counterclockwise.

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

source
Luxor.curveFunction.

Add a Bézier curve.

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

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

source

Geometry tools

You can find the midpoint between two points using midpoint().

The following code places a small pentagon (using ngon()) at the midpoint of each side of a larger pentagon:

sethue("red")
ngon(O, 100, 5, 0, :stroke)

sethue("darkgreen")
p5 = ngon(O, 100, 5, 0, vertices=true)

for i in eachindex(p5)
    pt1 = p5[mod1(i, 5)]
    pt2 = p5[mod1(i + 1, 5)]
    ngon(midpoint(pt1, pt2), 20, 5, 0, :fill)
end

arc

A more general function, between(), finds for a value x between 0 and 1 the corresponding point on a line defined by two points. So midpoint(p1, p2) and between(p1, p2, 0.5) should return the same point.

sethue("red")
p1 = Point(-150, 0)
p2 = Point(150, 40)
line(p1, p2)
strokepath()
for i in -0.5:0.1:1.5
    randomhue()
    circle(between(p1, p2, i), 5, :fill)
end

arc

Values less than 0.0 and greater than 1.0 appear to work well too, placing the point on the line if extended.

Luxor.midpointFunction.
midpoint(p1, p2)

Find the midpoint between two points.

source
midpoint(a)

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

source
Luxor.betweenFunction.
between(p1::Point, p2::Point, x)
between((p1::Point, p2::Point), x)

Find the point between point p1 and point p2 for x, where x is typically between 0 and 1, so these two should be equivalent:

between(p1, p2, 0.5)

and

midpoint(p1, p2)
source

center3pts() finds the radius and center point of a circle passing through three points which you can then use with functions such as circle() or arc2r().

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

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

returns (centerpoint, radius) of a circle. Then you can use circle() to place a circle, or arc() to draw an arc passing through those points.

If there's no such circle, then you'll see an error message in the console and the function returns (Point(0,0), 0).

source

intersection() finds the intersection of two lines.

pt1, pt2, pt3, pt4 = ngon(O, 100, 5, vertices=true)
line(pt1, pt2, :stroke)
line(pt3, pt4, :stroke)
flag, ip =  intersection(pt1, pt2, pt3, pt4)
if flag
    circle(ip, 5, :fill)
end

arc

intersection_line_circle() finds the intersection of a line and a circle. There can be 0, 1, or 2 intersection points.

l1 = Point(-100.0, -75.0)
l2 = Point(300.0, 100.0)
rad = 100
cpoint = Point(0, 0)
line(l1, l2, :stroke)
circle(cpoint, rad, :stroke)
nints, ip1, ip2 =  intersection_line_circle(l1, l2, cpoint, rad)
sethue("black")
if nints == 2
    circle(ip1, 8, :stroke)
    circle(ip2, 8, :stroke)
end

arc

intersection2circles() finds the area of the intersection of two circles. This example shows the areas of two circles, and the area of their intersection.

c1 = (O, 150)
c2 = (O + (100, 0), 150)

circle(c1... , :stroke)
circle(c2... , :stroke)

sethue("purple")
circle(c1... , :clip)
circle(c2... , :fill)
clipreset()

sethue("black")

text(string(150^2 * pi |> round), c1[1] - (125, 0))
text(string(150^2 * pi |> round), c2[1] + (100, 0))
sethue("white")
text(string(intersection2circles(c1..., c2...) |> round),
     midpoint(c1[1], c2[1]), halign=:center)

intersetion of two circles

Luxor.intersectionFunction.
intersection(p1::Point, p2::Point, p3::Point, p4::Point;
    commonendpoints = false,
    crossingonly = false,
    collinearintersect = false)

Find intersection of two lines p1-p2 and p3-p4

This returns a tuple: (boolean, point(x, y)).

Keyword options and default values:

crossingonly = false

If crossingonly = true, lines must actually cross. The function returns (false, intersectionpoint) if the lines don't actually cross, but would eventually intersect at intersectionpoint if continued beyond their current endpoints.

If false, the function returns (true, Point(x, y)) if the lines intersect somewhere eventually at intersectionpoint.

commonendpoints = false

If commonendpoints= true, will return (false, Point(0, 0)) if the lines share a common end point (because that's not so much an intersection, more a meeting).

Function returns (false, Point(0, 0)) if the lines are undefined.

If you want collinear points to be considered to intersect, set collinearintersect to true, although it defaults to false.

source
intersection_line_circle(p1::Point, p2::Point, cpoint::Point, r)

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

Return a tuple of (n, pt1, pt2)

where

  • n is the number of intersections, 0, 1, or 2

  • pt1 is first intersection point, or Point(0, 0) if none

  • pt2 is the second intersection point, or Point(0, 0) if none

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

source
intersection2circles(pt1, r1, pt2, r2)

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

source

getnearestpointonline() finds perpendiculars.

end1, end2, pt3 = ngon(O, 100, 3, vertices=true)
map(pt -> circle(pt, 5, :fill), [end1, end2, pt3])
line(end1, end2, :stroke)
arrow(pt3, getnearestpointonline(end1, end2, pt3))

arc

getnearestpointonline(pt1::Point, pt2::Point, startpt::Point)

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

source
pointlinedistance(p::Point, a::Point, b::Point)

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

source
Luxor.slopeFunction.
slope(pointA::Point, pointB::Point)

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

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

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

slope(Point(0, 100), O) |> rad2deg
270.0
source
Luxor.perpendicularFunction.
perpendicular(p::Point)

Returns point Point(p.y, -p.x).

source
Luxor.@polarMacro.
@polar (p)

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

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

produces

Luxor.Point(7.0710678118654755,7.071067811865475)
source
Luxor.polarFunction.
polar(r, theta)

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

polar(10, pi/4)

produces

Luxor.Point(7.071067811865475,7.0710678118654755)
source

Arrows

You can draw lines or arcs with arrows at the end with arrow(). For straight arrows, supply the start and end points. For arrows as circular arcs, you provide center, radius, and start and finish angles. You can optionally provide dimensions for the arrowheadlength and arrowheadangle of the tip of the arrow (angle in radians between side and center). The default line weight is 1.0, equivalent to setline(1)), but you can specify another.

arrow(O, Point(0, -65))
arrow(O, Point(100, -65), arrowheadlength=20, arrowheadangle=pi/4, linewidth=.3)
arrow(O, 100, pi, pi/2, arrowheadlength=25,   arrowheadangle=pi/12, linewidth=1.25)

arrows

Luxor.arrowFunction.
arrow(startpoint::Point, endpoint::Point;
    linewidth = 1.0,
    arrowheadlength = 10,
    arrowheadangle = pi/8)

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.

source
arrow(centerpos::Point, radius, startangle, endangle;
    linewidth = 1.0,
    arrowheadlength = 10,
    arrowheadangle = pi/8)

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.

source

Julia graphics

A couple of functions in Luxor provide you with instant access to the Julia logo, and the three colored circles:

for (pos, n) in Tiler(750, 250, 1, 2)
    gsave()
    translate(pos - Point(150, 100))
    if n == 1
        julialogo()
    elseif n == 2
        julialogo(action=:clip)
        for i in 1:500
            gsave()
            translate(rand(0:400), rand(0:250))
            juliacircles(10)
            grestore()
        end
        clipreset()
    end
    grestore()
end

get path

Function.
julialogo(;action=:fill, color=true)

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

julialogo()

If color is false, the logo will use the current color, and the dots won't be colored in the usual way.

The logo's dimensions are about 330 wide and 240 high, and the 0/0 point is at the bottom left corner. To place the logo by locating its center, do this:

gsave()
translate(-165, -120)
julialogo() # locate center at 0/0
grestore()

To use the logo as a clipping mask:

julialogo(action=:clip)

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

source
Luxor.juliacirclesFunction.
juliacircles(radius=100)

Draw the three Julia circles in color centered at the origin.

The distance of the centers of the circles from the origin is radius. The optional keyword arguments outercircleratio (default 0.75) and innercircleratio (default 0.65) control the radius of the individual colored circles relative to the radius. So you can get relatively smaller or larger circles by adjusting the ratios.

source

Miscellaneous

Hypotrochoids

hypotrochoid() makes hypotrochoids. The result is a polygon. You can either draw it directly, or pass it on for further polygon fun, as here, which uses offsetpoly() to trace round it a few times.

origin()
background("grey15")
sethue("antiquewhite")
setline(1)
p = hypotrochoid(100, 25, 55, :stroke, stepby=0.01, vertices=true)
for i in 0:3:15
    poly(offsetpoly(p, i), :stroke, close=true)
end

hypotrochoid

There's a matching epitrochoid() function.

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

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

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

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

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

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

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

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

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

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

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

source

Grids

If you have to position items regularly, you might find a use for a grid. Luxor provides a simple grid utility. Grids are lazy: they'll supply the next point on the grid when you ask for it.

Define a rectangular grid with GridRect, and a hexagonal grid with GridHex. Get the next grid point from a grid with nextgridpoint(grid).

grid = GridRect(O, 40, 80, (10 - 1) * 40)
for i in 1:20
    randomhue()
    p = nextgridpoint(grid)
    squircle(p, 20, 20, :fill)
    sethue("white")
    text(string(i), p, halign=:center)
end

grids

radius = 70
grid = GridHex(O, radius, 600)

for i in 1:15
    randomhue()
    p = nextgridpoint(grid)
    ngon(p, radius-5, 6, pi/2, :fillstroke)
    sethue("white")
    text(string(i), p, halign=:center)
end

grids

Luxor.GridRectType.
GridRect(startpoint, xspacing, yspacing, width, height)

Define a rectangular grid, to start at startpoint and proceed along the x-axis in steps of xspacing, then along the y-axis in steps of yspacing.

GridRect(startpoint, xspacing=100.0, yspacing=100.0, width=1200.0, height=1200.0)

For a column, set the xspacing to 0:

grid = GridRect(O, 0, 40)

To get points from the grid, use nextgridpoint(g::Grid).

julia> grid = GridRect(O, 0, 40);
julia> nextgridpoint(grid)
Luxor.Point(0.0,0.0)

julia> nextgridpoint(grid)
Luxor.Point(0.0,40.0)

When you run out of grid points, you'll wrap round and start again.

source
Luxor.GridHexType.
GridHex(startpoint, radius, width=1200.0, height=1200.0)

Define a hexagonal grid, to start at startpoint and proceed along the x-axis and then along the y-axis, radius is the radius of a circle that encloses each hexagon. The distance in x between the centers of successive hexagons is:

\[\frac{\sqrt{(3)} radius}{2}\]

To get the next point from the grid, use nextgridpoint(g::Grid).

When you run out of grid points, you'll wrap round and start again.

source
Luxor.nextgridpointFunction.
nextgridpoint(g::GridRect)

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

source
nextgridpoint(g::GridHex)

Returns the next available grid point of a hexagonal grid.

source

Cropmarks

If you want cropmarks (aka trim marks), use the cropmarks() function, supplying the centerpoint, followed by the width and height:

cropmarks(O, 1200, 1600)
cropmarks(O, paper_sizes["A0"]...)
sethue("red")
box(O, 150, 150, :stroke)
cropmarks(O, 150, 150)

cropmarks

Luxor.cropmarksFunction.
cropmarks(center, width, height)

Draw cropmarks (also known as trim marks).

source

Bars

For simple bars, use the bars() function, supplying an array of numbers:

fontsize(7)
sethue("black")
v = rand(-100:100, 60)
bars(v)

bars

To change the way the bars and labels are drawn, define some functions and pass them as keyword arguments to bars():

function mybarfunction(low::Point, high::Point, value; extremes=[0, 1])
    @layer begin
        sethue(rescale(value, extremes[1], extremes[2], 0, 1), 0.0, 1.0)
        circle(high, 8, :fill)
        setline(1)
        sethue("blue")
        line(low, high + (0, 8), :stroke)
        sethue("white")
        text(string(value), high, halign=:center, valign=:middle)
    end
end

function mylabelfunction(low::Point, high::Point, value; extremes=[0, 1])
    @layer begin
        translate(low)
        rotate(-pi/2)
        text(string(value,"/", extremes[2]), O - (10, 0), halign=:right, valign=:middle)
    end
end

v = rand(1:200, 30)
bars(v, xwidth=25, barfunction=mybarfunction, labelfunction=mylabelfunction)

bars 1

Luxor.barsFunction.
bars(values::Array;
        yscale = 100,
        xwidth = 10,
        labels = true,
        barfunction = f,
        labelfunction = f,
    )

Draw some bars where each bar is the height of a value in the array.

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

mybarfunction(bottom::Point, top::Point, value; extremes=[a, b])

mylabelfunction(bottom::Point, top::Point, value; extremes=[a, b])

and pass them like this:

bars(v, yscale=10, xwidth=10, barfunction=mybarfunction)
bars(v, xwidth=15, yscale=10, labelfunction=mylabelfunction)

To suppress the text labels, use optional keyword labels=false.

source