Styling

Color and opacity

For color definitions and conversions, you can use Colors.jl.

setcolor() and sethue() apply a single solid or transparent color to shapes. setblend() applies a smooth transition between two or more colors.

The difference between the setcolor() and sethue() functions is that sethue() is independent of alpha opacity, so you can change the hue without changing the current opacity value.

Named colors, such as "gold", or "lavender", can be found in Colors.color_names. This code shows the first 625 colors.

cols = collect(Colors.color_names)
tiles = Tiler(800, 500, 25, 25)
for (pos, n) in tiles
    sethue(cols[n][1])
    box(pos, tiles.tilewidth, tiles.tileheight, :fill)
    clab = convert(Lab, parse(Colorant, cols[n][1]))
    labelbrightness = 100 - clab.l
    sethue(convert(RGB, Lab(labelbrightness, clab.b, clab.a)))
    text(string(cols[n][1]), pos, halign=:center)
end

line endings

Some fiddling with Lab colors adjusts the label color to make it stand out against the background.

Luxor.sethueFunction.
sethue("black")
sethue(0.3,0.7,0.9)

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.

source
sethue(col::ColorTypes.Colorant)

Set the color without changing current opacity:

source
sethue(0.3, 0.7, 0.9)

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

source
Luxor.setcolorFunction.
setcolor("gold")
setcolor("darkturquoise")

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

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

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

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

(1.0,0.0,0.0,0.8)

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

(1.0,0.0,0.0)
source
setcolor(r, g, b)
setcolor(r, g, b, alpha)
setcolor(color)
setcolor(col::ColorTypes.Colorant)

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
source
Luxor.setblendFunction.
setblend(blend)

Start using the named blend for filling graphics.

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

source
Luxor.setopacityFunction.
setopacity(alpha)

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

source
Luxor.randomhueFunction.
randomhue()

Set a random hue.

Choose a random color without changing the current alpha opacity.

source
Luxor.randomcolorFunction.
randomcolor()

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

source

Line styles

The set- functions control subsequent lines' width, end shapes, join behavior, and dash pattern:

for l in 1:3
    sethue("black")
    setline(20)
    setlinecap(["butt", "square", "round"][l])
    textcentred(["butt", "square", "round"][l], 80l, 80)
    setlinejoin(["round", "miter", "bevel"][l])
    textcentred(["round", "miter", "bevel"][l], 80l, 120)
    poly(ngon(Point(80l, 0), 20, 3, 0, vertices=true), :strokepreserve, close=false)
    sethue("white")
    setline(1)
    stroke()
end

line endings

setline(12)
patterns = ["solid", "dotted", "dot", "dotdashed", "longdashed",
  "shortdashed", "dash", "dashed", "dotdotdashed", "dotdotdotdashed"]
tiles =  Tiler(400, 250, 10, 1, margin=10)
for (pos, n) in tiles
    setdash(patterns[n])
    textright(patterns[n], pos.x - 20, pos.y + 4)
    line(pos, Point(400, pos.y), :stroke)
end

dashes

Luxor.setlineFunction.
setline(n)

Set the line width.

source
Luxor.setlinecapFunction.
setlinecap(s)

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

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

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

source
Luxor.setdashFunction.
setlinedash("dot")

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

source
Luxor.fillstrokeFunction.
fillstroke()

Fill and stroke the current path.

source
Luxor.strokeFunction.
stroke()

Stroke the current path with the current line width, line join, line cap, and dash settings. The current path is then cleared.

source
Base.fillFunction.
fill()

Fill the current path with current settings. The current path is then cleared.

source
Luxor.strokepreserveFunction.
strokepreserve()

Stroke the current path with current line width, line join, line cap, and dash settings, but then keep the path current.

source
Luxor.fillpreserveFunction.
fillpreserve()

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

source
Luxor.paintFunction.
paint()

Paint the current clip region with the current settings.

source
Luxor.do_actionFunction.
do_action(action)

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

source

Blends

A blend is a color gradient. Use setblend() to select a blend in the same way that you'd use setcolor() and sethue() to select a solid color.

You can make linear or radial blends. Use blend() in either case.

To create a simple linear blend between two colors, supply two points and two colors to blend():

orangeblue = blend(Point(-200, 0), Point(200, 0), "orange", "blue")
setblend(orangeblue)
box(O, 400, 100, :fill)
axes()

linear blend

And for a radial blend, provide two point/radius pairs, and two colors:

greenmagenta = blend(Point(0, 0), 5, Point(0, 0), 150, "green", "magenta")
setblend(greenmagenta)
box(O, 400, 200, :fill)
axes()

radial blends

You can also use blend() to create an empty blend. Then you use addstop() to define the locations of specific colors along the blend, where 0 is the start, and 1 is the end.

goldblend = blend(Point(-200, 0), Point(200, 0))
addstop(goldblend, 0.0,  "gold4")
addstop(goldblend, 0.25, "gold1")
addstop(goldblend, 0.5,  "gold3")
addstop(goldblend, 0.75, "darkgoldenrod4")
addstop(goldblend, 1.0,  "gold2")
setblend(goldblend)
box(O, 400, 200, :fill)
axes()

blends from scratch

When you define blends, the location of the axes (eg the current workspace as defined by translate(), etc.), is important. In the first of the two following examples, the blend is selected before the axes are moved with translate(pos). The blend 'samples' the original location of the blend's definition.

goldblend = blend(Point(0, 0), Point(200, 0))
addstop(goldblend, 0.0,  "gold4")
addstop(goldblend, 0.25, "gold1")
addstop(goldblend, 0.5,  "gold3")
addstop(goldblend, 0.75, "darkgoldenrod4")
addstop(goldblend, 1.0,  "gold2")
setblend(goldblend)
tiles = Tiler(600, 200, 1, 5, margin=10)
for (pos, n) in tiles
    gsave()
    setblend(goldblend)
    translate(pos)
    ellipse(O, tiles.tilewidth, tiles.tilewidth, :fill)
    grestore()
end

blends 1

Outside the range of the original blend's definition, the same color is used, no matter how far away from the origin you go (there are Cairo options to change this). But in the next example, the blend is relocated to the current axes, which have just been moved to the center of the tile. The blend refers to 0/0 each time, which is at the center of shape.

goldblend = blend(Point(0, 0), Point(200, 0))
addstop(goldblend, 0.0,  "gold4")
addstop(goldblend, 0.25, "gold1")
addstop(goldblend, 0.5,  "gold3")
addstop(goldblend, 0.75, "darkgoldenrod4")
addstop(goldblend, 1.0,  "gold2")
setblend(goldblend)
tiles = Tiler(600, 200, 1, 5, margin=10)
for (pos, n) in tiles
    gsave()
    translate(pos)
    setblend(goldblend)
    ellipse(O, tiles.tilewidth, tiles.tilewidth, :fill)
    grestore()
end

blends 2

Luxor.blendFunction.
blend(from::Point, to::Point)

Create an empty linear blend.

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

To add colors, use addstop().

source
blend(centerpos1, rad1, centerpos2, rad2, color1, color2)

Create a radial blend.

Example:

redblue = blend(
    pos, 0,                   # first circle center and radius
    pos, tiles.tilewidth/2,   # second circle center and radius
    "red",
    "blue"
    )
source
blend(pt1::Point, pt2::Point, color1, color2)

Create a linear blend.

Example:

redblue = blend(pos, pos, "red", "blue")
source
blend(from::Point, startradius, to::Point, endradius)

Create an empty radial blend.

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

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

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

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

Example:

blendredblue = blend(Point(0, 0), 0, Point(0, 0), 1)
addstop(blendredblue, 0, setcolor(sethue("red")..., .2))
addstop(blendredblue, 1, setcolor(sethue("blue")..., .2))
addstop(blendredblue, 0.5, sethue(randomhue()...))
addstop(blendredblue, 0.5, setcolor(randomcolor()...))
source

Using blendadjust()

You can use blendadjust() to modify the blend so that objects scaled and positioned after the blend was defined are rendered correctly.

setline(20)

# first line
blendgoldmagenta = blend(Point(-100, 0), Point(100, 0), "gold", "magenta")
setblend(blendgoldmagenta)
line(Point(-100, -50), Point(100, -50))
stroke()

# second line
blendadjust(blendgoldmagenta, Point(50, 0), 0.5, 0.5)
line(O, Point(100, 0))
stroke()

# third line
blendadjust(blendgoldmagenta, Point(-50, 50), 0.5, 0.5)
line(Point(-100, 50), Point(0, 50))
stroke()

# fourth line
gsave()
translate(0, 100)
scale(0.5, 0.5)
setblend(blendgoldmagenta)
line(Point(-100, 0), Point(100, 0))
stroke()
grestore()

blends adjust

The blend is defined to span 200 units, horizontally centered at 0/0. The top line is also 200 units long and centered horizontally at 0/0, so the blend is rendered exactly as you'd hope.

The second line is only half as long, at 100 units, centered at 50/0, so blendadjust() is used to relocate the blend's center to the point 50/0 and scale it by 0.5 (100/200).

The third line is also 100 units long, centered at -50/0, so again blendadjust() is used to relocate the blend's center and scale it.

The fourth line shows that you can translate and scale the axes instead of adjusting the blend, if you use setblend() again in the new scene.

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

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

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

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

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

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

then use setblend():

setblend(blendgoldmagenta)
source