This post was inspired mostly by the arrival of the new Julia package Literate.jl by Fredrik Ekre.
Literate lets you write a Julia source file, a Markdown blog post, and a Jupyter notebook all at the same time. It's magic, or, at least, indistinguishable from magic... As a result, you might be able to find a Jupyter notebook version of this Markdown-converted Julia source file somewhere nearby (look in the github repo for this blog, perhaps). The code uses the following packages, Literate, Luxor, Colors, Roots, Fontconfig, DataFrames, IterTools, ColorSchemes, and should work in Julia v1.0. [1]
Luxor provides some support for Bézier curves, but there's no room for any more documentation—it's already too big. So the intention of this post is to provide some of the missing information about what's up with Bézier curves and how you might use them. And I confess in advance to wasting some of your bandwidth with some pointlessly colourful graphics. [2]
There's not a lot of mathematical material here, but fortunately the internet is awash with high-quality information about Bézier curves. The two articles you should definitely read instead of this post, or at least before, are:
https://pomax.github.io/bezierinfo/ An exhaustive and exhausting examination of Bézier curves by Mike 'Pomax' Kamermans, Mozilla JavaScript guru, complete with interactive JavaScript graphics.
http://mae.engr.ucdavis.edu/~farouki/bernstein.pdf A paper exploring the history and mathematics of curves by Professor Rida T. Farouki (University of California Davis).
The story is quite well known: two engineers employed in the French car industry, Paul de Faget de Casteljau, at Citroen, and Pierre Etienne Bézier, at Renault, worked—mostly independently—on the mathematics of curves in the early 1960s, as the industry made its first tentative steps towards using computers for design and production.
(This is the Citroen DS, which was once voted the most beautiful car ever made, apparently. Image from the WikiMedia Commons photographed by Klugschnacker)
De Casteljau and Bézier were interested in mathematical tools that would allow designers to intuitively construct and manipulate complex shapes. This problem was especially critical for “free–form” shapes that couldn't easily be specified by centre points, axes, angles, and dimensions. The motivation was also partly to replace the laborious, variable, and expensive process of sculpting clay models to specify the desired shape.
De Casteljau found some resistance when his mathematical researches were introduced into the design studio. He observed that:
the designers were astonished and scandalized. Was it some kind of joke? It was considered nonsense to represent a car body mathematically. It was enough to please the eye, the word 'accuracy' had no meaning. [quoted in Farouki]
Bézier popularized, but did not actually create, what we know today as the Bézier curve. He mainly developed the notation, and devised the idea of nodes with attached "control handles", which the designers could use to adjust the shapes as easily as they used the turn indicators on their beloved Citroen DSs.
De Casteljau is also remembered for the algorithm that bears his name.
With the lingering thought of old Renaults and Citroens in mind, it's tempting to think of a Bézier curve as a line that takes, not the shortest distance between two points, but takes instead the scenic route.
Let's define four points:
using Luxor
P1 = Point(-200, 0)
CP1 = Point(-100, -100)
CP2 = Point(100, -100)
P2 = Point(200, 0)
@png begin
circle.([P1, CP1, CP2, P2], 5, :fill)
label.(["P1", "CP1", "CP2", "P2"], :N, [P1, CP1, CP2, P2], offset=10)
end 800 300 "../images/bezier/fourpoints.png"
The first and last points, P1 and P2, are the start and end of the line. The second point, CP1, controls the direction of the line as it leaves the first point, and the third point, CP2, determines the way the line approaches the fourth point. PostScript guru Don Lancaster [3] uses the terms ‘influence point’ and ‘enthusiasm’—so the second influence point determines the enthusiasm with which the curve travels towards its final destination.
The graphics primitive curve()
function draws a Bézier curve between P1 and P2, taking into account the positions of control points CP1 and CP2. It takes just three points, using the current position as the starting point.
using Colors
@png begin
sethue("red")
label.(["P1", "CP1", "CP2", "P2"], :N, [P1, CP1, CP2, P2])
line(P1, CP1,:stroke)
line(CP2, P2,:stroke)
sethue("black")
move(P1)
curve(CP1, CP2, P2)
strokepath()
circle.([P1, CP1, CP2, P2], 3, :fill)
end 800 300 "../images/bezier/curve.png"
The control points are like handles, controlling the shape of the curve. If you've used Adobe Illustrator or some other vector graphics software, you'll be familiar with the idea of interactively dragging the handles around to get interesting curves. In Luxor, though, we sacrifice interactivity in favour of ruthless machine-driven automation. In the following animation, the control handles explore the geometry of a couple of hypotrochoids, while the helpless Bézier curve is pinned between them and forced into sinuous contortions:
Can I make this in Adobe Illustrator? Hold my beer...
While you're waiting, have a look at another animation; this is my artist's impression of the De Casteljau algorithm dividing the control polygons around a Bézier curve as the parameter n
moves from 0 to 1. The idea is that as p1
divides A
to A1
, p2
divides A1
to B1
and p3
divides B1
to B
. So, pp1
divides p1
to p2
, and pp2
divides p2
to p3
. And you keep doing this until you can't divide any more, and eventually the point P
plots the course of the final Bézier curve. The red and blue parts of the curve show that this technique is also a good way to split a single Bézier curve into two separate ones, and the red and blue parts are separate control polygons.
In Luxor, a BezierPathSegment
type contains four 2D points, stored in the fields p1
, cp1
, cp2
, and p2
, and a BezierPath
is an array of one or more of these BezierPathSegments. The drawbezierpath()
function draws a BezierPath or BezierPathSegment, with similar results to the curve()
function:
@png begin
P1 = Point(-200, 0)
CP1 = Point(-100, -100)
CP2 = Point(100, -100)
P2 = Point(200, 0)
sethue("red")
label.(["P1", "CP1", "CP2", "P2"], :N, [P1, CP1, CP2, P2])
line(P1, CP1,:stroke)
line(P2, CP2,:stroke)
sethue("black")
beziersegment = BezierPathSegment(P1, CP1, CP2, P2)
circle.(beziersegment, 3, :fill)
drawbezierpath(beziersegment, :stroke)
end 800 250 "../images/bezier/drawbezierpath.png"
This last function, drawbezierpath()
is a typical Luxor drawing function, in that you can provide :fill
as an alternative action to :stroke
.
An easy way to make a BezierPath is to use makebezierpath()
and supply a polygon. For example, let's make a triangle with ngon()
and use it as the skeleton for a new Bézier path:
@png begin
sethue("red")
triangle = ngon(O, 120, 3, vertices=true)
poly(triangle, :stroke, close=true)
sethue("black")
bezierpath = makebezierpath(triangle)
drawbezierpath(bezierpath, :stroke)
circle.(triangle, 5, :fill)
for bps in bezierpath
circle.(bps, 3, :fill)
line(bps[1], bps[2], :stroke)
line(bps[3], bps[4], :stroke)
end
label.(["p1", "p2", "p3"], [:S, :NW, :E], triangle, offset=10)
end 800 400 "../images/bezier/makebezierpath.png"
Here makebezierpath()
converted the three points into an array of three separate Bézier path segments. The control points are positioned so that the curve flows freely from one segment to the segment.
Bézier curves can have straight bits too. This animation shows the control points moving towards the points they're controlling. When they merge, the Bézier path appears to become a series of straight lines:
function frame(scene, framenumber)
background("white")
eased_n = scene.easingfunction(framenumber, 0, 1, scene.framerange.stop)
sethue("red")
triangle = ngon(O, 120, 3, vertices=true)
poly(triangle, :stroke, close=true)
sethue("black")
bezierpath = makebezierpath(triangle)
newbezierpath = BezierPath()
for bps in bezierpath
push!(newbezierpath,
BezierPathSegment(bps.p1,
between(bps.cp1, bps.p1, eased_n),
between(bps.cp2, bps.p2, eased_n),
bps.p2))
end
for bps in newbezierpath
circle.(bps, 3, :fill)
line(bps.p1, bps.cp1, :stroke)
line(bps.cp2, bps.p2, :stroke)
end
drawbezierpath(newbezierpath, :stroke)
end
width, height = (256, 256)
bedtimemovie = Movie(width, height, "bedtimemovie")
# probably requires ffmpeg installed
animate(bedtimemovie, [Scene(bedtimemovie, frame, 1:150, easingfunction=easeoutquad)], creategif=true, framerate=30, pathname="../images/bezier/bedtimemovie.gif")
This is a process known to very young Adobe Illustrator users as ‘putting the Bézier handles to bed’.
It's also fun to move the control points somewhere else. Here, they're multiplied by 2, while the first and last points are left unchanged:
@png begin
sethue("red")
triangle = ngon(O, 100, 3, vertices=true)
poly(triangle, :stroke, close=true)
sethue("purple")
bezierpath = makebezierpath(triangle)
drawbezierpath(bezierpath, :stroke)
sethue("magenta")
for bps in bezierpath
bps = BezierPathSegment(bps[1], 2bps[2], 2bps[3], bps[4])
drawbezierpath(bps, :stroke)
end
end 800 400 "../images/bezier/movecontrolpoints.png"
We could make even more copies, multiplying the control points each time through:
@png begin
setline(.5)
pgon = ngon(O, 100, 3, vertices=true)
bezierpath = makebezierpath(pgon)
for bpseg in bezierpath
for i in 1:20
bpseg.cp1 *= 1.05
bpseg.cp2 *= 1.05
drawbezierpath(bpseg, :stroke)
end
end
end 800 400 "../images/bezier/movecontrolpointsmore.png"
Try changing the initial triangle to a pentagon or heptagon by changing the 3
in ngon()
. And try multiplying by less than 1.05 too...
Should you ever want to draw Bézier curves "the hard way", try this. The standard Bézier function is available in Luxor as bezier()
, and we could draw a small hue-varying circle at each point:
@png begin
setopacity(0.6)
P1 = Point(-250, 0)
CP1 = Point(-200, -150)
CP2 = Point(200, -150)
P2 = Point(250, 0)
circle.([P1, CP1, CP2, P2], 3, :fill)
for u in 0.0:0.025:1.0
sethue(Colors.HSV(rescale(u, 0, 1, 0, 360), 1, 1))
pt = bezier(u, P1, CP1, CP2, P2)
circle(pt, 5, :fill)
end
end 800 400 "../images/bezier/thehardway.png"
What happens if you step u
from say, -25.0 to 25.0? And is that supposed to happen? (Spoiler: the curve doubles back and shoots off to (±∞/∞)
.)
Suppose you wanted to draw a Bézier curve but you didn't know where the control points were, but you did know a couple of points that should lie on the line? Again, Luxor has the answer for you, in the form of a function called bezierfrompoints()
. You supply four points, and the function returns the points of the Bézier curve that passes through them.
@png begin
sethue("grey80")
box(O, 120, 100, :stroke)
corners = box(O, 120, 100, vertices=true)
label.(["1", "2","3", "4"], :se, corners, offset=10)
sethue("red")
circle.(corners, 5, :fill)
startpoint, cp1, cp2, endpoint = bezierfrompoints(corners...)
sethue("purple")
line(startpoint, cp1, :stroke)
line(endpoint, cp2, :stroke)
sethue("turquoise")
circle.([cp1, cp2, startpoint, endpoint], 3, :fill)
sethue("blue")
drawbezierpath(BezierPathSegment(startpoint, cp1, cp2, endpoint), :stroke)
end 800 300 "../images/bezier/bezierfrompoints.png"
Note that this function returns all four points, but of course you already knew the first and last ones (in corners
), you just wanted the two control points.
In Luxor the three main ways to make graphic lines and shapes are: paths, polygons, and BezierPaths. Paths are the fundamental building blocks of graphics, consisting of one or more sequences of straight and Bézier curves. A polygon is an array of points, which will be converted to a path when you draw it. And BezierPaths consist of a list of BezierPathSegments, which will also be converted to ordinary paths when they're drawn.
It's useful to be able to convert between the different types:
| Function | Converts |
|---|---|
makebezierpath(pgon) | polygon to BezierPath |
pathtopoly() | current path to array of polygons |
pathtobezierpaths() | current path to array of BezierPaths |
beziertopoly(bpseg) | BezierPathSegment to polygon |
bezierpathtopoly(bezierpath) | BezierPath to polygon |
bezierfrompoints(p1, p2, p3, p4) | convert four points to BezierPathSegment |
Having witnessed the ability of a simple Bézier curve to adopt so many shapes, it's a bit surprising to find out that you can't use a Bézier curve to draw a circle. Well, it can do a very good impression of one, of course, but mathematically it can't produce a purely circular curve. You can see this if you draw a very large circle and a very large matching Bézier segment. For a circle with radius 10 meters (and that's one big PDF), the discrepancy between the pure circle and the Bézier approximation isn't much bigger than the size of this period/full stop.←
We non-scientists are lucky in not having to worry about errors of this magnitude...
In the above picture, the red circle is made by circle()
, the green one is made with Bézier curves (as used by circlepath()
and ellipse()
).
To draw approximate circles using Bézier curves, you need to know the magic number, usually called kappa, which has the value 0.552284.... We're looking at a Bézier curve pretending to be a circular quadrant, with control points positioned at a certain distance kappa from the end points. But how far? What value of kappa—what length of handle—will give us the most circular curve?
A picture of the problem, using a deliberately not-very-good guess at a value for kappa of 0.5:
@png begin
scale(3)
fontsize(7)
translate(0, -50)
setline(1)
radius = 100
kappa_guess = .5
sethue("red")
arc(O, radius, 0, pi/2, :stroke)
sethue("black")
quadrant = BezierPathSegment(
Point(0, radius),
Point(kappa_guess * radius, radius),
Point(radius, kappa_guess * radius),
Point(radius, 0))
circle.(quadrant, 1, :fill)
arrow(quadrant.p1, quadrant.cp1, arrowheadlength=5)
arrow(quadrant.p2, quadrant.cp2, arrowheadlength=5)
drawbezierpath(quadrant, :stroke)
arrow(O, bezier(0.5, quadrant...))
label.(["(0, 1)", "(k, 1)", "(1, k)", "(1, 0)"], [:S, :S, :E, :E], quadrant)
label("kappa_guess = 0.5", :S, midpoint(quadrant.p1, quadrant.cp1), offset=15)
end 800 450 "../images/bezier/kappaguess.png"
To solve this, we'll define a function that takes a value for kappa
and works out the radius at the center of the Bézier curve.
_radiusofbezier(κ) = distance(O,
bezier(0.5, BezierPathSegment(
Point(0, 1),
Point(κ * 1, 1),
Point(1, κ * 1),
Point(1, 0)
)...))
radiusofbezier(x) = _radiusofbezier(x) - 1
And we'll use one of the many excellent packages in JuliaMath, Roots.jl, to find the zero point:
using Roots
find_zero(radiusofbezier, (0, 1), verbose=true)
Results of univariate zero finding:
* Converged to: 0.5522847498307936
* Algorithm: Roots.Bisection64()
* iterations: 59
* function evaluations: 61
* stopped as |f(x_n)| ≤ max(δ, max(1,|x|)⋅ϵ) using δ = atol, ϵ = rtol
Trace:
x_0 = 0.0000000000000000, f(x_0) = -0.2928932188134524
x_1 = 0.0000000000000000, f(x_1) = -0.2928932188134524
x_2 = 0.0000000000000000, f(x_2) = -0.2928932188134524
x_3 = 0.0000000000000000, f(x_3) = -0.2928932188134524
x_4 = 0.0000000000000000, f(x_4) = -0.2928932188134524
x_5 = 0.0000000002401066, f(x_5) = -0.2928932186861167
x_6 = 0.0000154972076416, f(x_6) = -0.2928850001779929
x_7 = 0.0039367675781250, f(x_7) = -0.2908054325256169
x_8 = 0.0627441406250000, f(x_8) = -0.2596181133267076
x_9 = 0.2504882812500000, f(x_9) = -0.1600517471037238
x_10 = 0.5004882812500000, f(x_10) = -0.0274692256312462
x_11 = 0.5004882812500000, f(x_11) = -0.0274692256312462
x_12 = 0.5004882812500000, f(x_12) = -0.0274692256312462
x_13 = 0.5004882812500000, f(x_13) = -0.0274692256312462
x_14 = 0.5317077636718750, f(x_14) = -0.0109125948370147
x_15 = 0.5473175048828125, f(x_15) = -0.0026342794398989
x_16 = 0.5473175048828125, f(x_16) = -0.0026342794398989
x_17 = 0.5512199401855469, f(x_17) = -0.0005647005906200
x_18 = 0.5512199401855469, f(x_18) = -0.0005647005906200
x_19 = 0.5521955490112305, f(x_19) = -0.0000473058783003
x_20 = 0.5521955490112305, f(x_20) = -0.0000473058783003
x_21 = 0.5521955490112305, f(x_21) = -0.0000473058783003
x_22 = 0.5521955490112305, f(x_22) = -0.0000473058783003
x_23 = 0.5522565245628357, f(x_23) = -0.0000149687087803
x_24 = 0.5522565245628357, f(x_24) = -0.0000149687087803
x_25 = 0.5522717684507370, f(x_25) = -0.0000068844164003
x_26 = 0.5522793903946877, f(x_26) = -0.0000028422702103
x_27 = 0.5522832013666630, f(x_27) = -0.0000008211971153
x_28 = 0.5522832013666630, f(x_28) = -0.0000008211971153
x_29 = 0.5522841541096568, f(x_29) = -0.0000003159288415
x_30 = 0.5522846304811537, f(x_30) = -0.0000000632947047
x_31 = 0.5522846304811537, f(x_31) = -0.0000000632947047
x_32 = 0.5522847495740280, f(x_32) = -0.0000000001361705
x_33 = 0.5522847495740280, f(x_33) = -0.0000000001361705
x_34 = 0.5522847495740280, f(x_34) = -0.0000000001361705
x_35 = 0.5522847495740280, f(x_35) = -0.0000000001361705
x_36 = 0.5522847495740280, f(x_36) = -0.0000000001361705
x_37 = 0.5522847495740280, f(x_37) = -0.0000000001361705
x_38 = 0.5522847495740280, f(x_38) = -0.0000000001361705
x_39 = 0.5522847495740280, f(x_39) = -0.0000000001361705
x_40 = 0.5522847495740280, f(x_40) = -0.0000000001361705
x_41 = 0.5522847498066312, f(x_41) = -0.0000000000128140
x_42 = 0.5522847498066312, f(x_42) = -0.0000000000128140
x_43 = 0.5522847498066312, f(x_43) = -0.0000000000128140
x_44 = 0.5522847498066312, f(x_44) = -0.0000000000128140
x_45 = 0.5522847498211689, f(x_45) = -0.0000000000051041
x_46 = 0.5522847498284378, f(x_46) = -0.0000000000012493
x_47 = 0.5522847498284378, f(x_47) = -0.0000000000012493
x_48 = 0.5522847498302550, f(x_48) = -0.0000000000002855
x_49 = 0.5522847498302550, f(x_49) = -0.0000000000002855
x_50 = 0.5522847498307093, f(x_50) = -0.0000000000000447
x_51 = 0.5522847498307093, f(x_51) = -0.0000000000000447
x_52 = 0.5522847498307093, f(x_52) = -0.0000000000000447
x_53 = 0.5522847498307660, f(x_53) = -0.0000000000000145
x_54 = 0.5522847498307660, f(x_54) = -0.0000000000000145
x_55 = 0.5522847498307802, f(x_55) = -0.0000000000000070
x_56 = 0.5522847498307873, f(x_56) = -0.0000000000000032
x_57 = 0.5522847498307909, f(x_57) = -0.0000000000000013
x_58 = 0.5522847498307927, f(x_58) = -0.0000000000000004
x_59 = 0.5522847498307936, f(x_59) = 0.0000000000000000
0.5522847498307936
So that's kappa
.
Alternatively, we can use algebraic sorcery and the parametric equation for the Bézier cubic function to conjure the value from the following incantations:
\[ \mathbf{p1} = (0, 1) \\ \mathbf{cp1} = (k, 1) \\ \mathbf{cp2} = (1, k) \\ \mathbf{p2} = (1, 0) \\ B(u) = \mathbf{p1}(1 - u)³ + \mathbf{cp1} 3u(1 - u)² + \mathbf{cp2}3u²(1 - u) + \mathbf{p2} u³ \\ u = 0.5 \\ \sqrt{0.5} = \frac{\mathbf{p1}}{8} + \frac{3\mathbf{cp1}}{8} + \frac{3\mathbf{cp2}}{8} + \frac{\mathbf{p2}}{} \\ x: \sqrt{0.5} = \frac{0}{8} + \frac{3 k}{8} + \frac{3}{8} + \frac{1}{8} \\ y: \sqrt{0.5} = \frac{1}{8} + \frac{3 }{8} + \frac{3 k}{8} + \frac{0}{8} \\ \frac{\sqrt{2}}{2} = 3 \frac{k}{8} + \frac{4}{8} \\ \sqrt{2} = 3 \frac{k}{4} + \frac{4}{4} \\ \sqrt{2} = 3 \frac{k}{4} + 1 \\ \sqrt{2} - 1 = 3 \frac{k}{4} \\ 3 k = 4 (\sqrt{2} - 1) \\ k = \frac{4}{3} (\sqrt{2} - 1) \\ k = 0.5522847498307936 \\ \]I suspect most applications simply hard-code the magic number 0.552284... directly.
The following code shows how we could animate the process of changing the length of the handles by changing the value of kappa. Blink and you'll miss the sweet spot, though, and the movement of the handles is imperceptible, so perhaps this isn't the best way of illustrating the construction.
function frame(scene, framenumber)
background("white")
eased_n = scene.easingfunction(framenumber, 0, 1, scene.framerange.stop)
radius = 250
setline(1)
sethue("black")
kappa = rescale(eased_n, 0, 1, 0.525, 0.575)
handlelength = (radius/2) * kappa
left = Point(-radius/2, 0)
top = Point(0, -radius/2)
right = Point(radius/2, 0)
bottom = Point(0, radius/2)
line(left, left + (0, -handlelength), :stroke)
line(top + (-handlelength, 0), top, :stroke)
circle.([left, top, right, bottom], 2, :fill)
circle.([left + (0, -handlelength), top + (-handlelength, 0)], 2, :fill)
line(left, left + (0, -handlelength), :stroke)
line(top + (-handlelength, 0), top, :stroke)
circle.([left, top, right, bottom], 2, :fill)
circle.([left + (0, -handlelength), top + (-handlelength, 0)], 2, :fill)
move(left)
curve(left + (0, -handlelength),
top + (-handlelength, 0),
top)
curve(top + (handlelength, 0),
right + (0, -handlelength),
right)
curve(right + (0, handlelength),
bottom + (handlelength, 0),
bottom)
curve(bottom + (-handlelength, 0),
left + (0, handlelength),
left)
if isapprox(kappa, 0.55228, atol=0.001)
sethue("red")
fillpath()
sethue("black")
else
strokepath()
end
fontface("Menlo")
fontsize(20)
text(string(round(kappa, digits=4)), halign=:center)
end
width, height = (400, 400)
circlefrombezier = Movie(width, height, "circlefrombezier")
animate(circlefrombezier, [Scene(circlefrombezier, frame, 1:150, easingfunction=easeoutquad)], creategif=true, framerate=30, pathname="../images/bezier/circlefrombezier.gif")
Luxor already provides a Luxor.circlepath()
function that uses four Bézier curve segments to build a path that draws a circle. The main advantage of using this instead of the default arc
-based circle is that it's easier to build circles with holes:
@png begin
background("moccasin")
sethue("darkorange")
circlepath(O, 200, :path)
newsubpath()
circlepath(O + (100, -40), 70, :path, reversepath=true)
newsubpath()
circlepath(O - (100, 40), 70, :path, reversepath=true)
fillpreserve()
setline(8)
sethue("skyblue")
strokepath()
end 800 500 "../images/bezier/circularpaths.png"
In practice, not being able to draw perfect circles with Bezier curves isn't a big problem. Font designers (you knew this was going to go ‘all typographical’ sooner or later) are big users of Bézier curves, and typically they don't like drawing perfect circles anyway, because of the optical corrections required to make things ‘look right’.
 The following is one of the simplest, but most people would be prepared to bet that the horizontal bar is thicker and shorter than the vertical bar. I had to draw a grid to double-check...
@png begin
background("white")
sethue("gray90")
g = GridRect(BoundingBox()[1], 15, 15, 30 * 20, 30 * 20)
for i in 1:1600
circle(nextgridpoint(g), 0.5, :fill)
end
b = box(O - (15, -60), O + (15, -60), vertices=true)
sethue("black")
poly(b, :fill)
translate(0, -90)
rotate(pi/2)
poly(b, :fill)
end 300 300 "../images/bezier/opticalillusion.png"
It's something to do with how our eyes, set side by side and trained to move side to side horizontally with great speed and precision, underestimate width. Type designers spend much of their time adjusting the relative widths and thicknesses of letter shapes so that illusions like this are compensated for in advance. What happens if you turn your display on its side (apart from possibly spilling your coffee)?
Here's a short script to examine the bounding boxes of the inner and outer loops of the letter ‘o’ in various fonts.
function drawletteraspect(pos, font, thefontsize, str)
@layer begin
translate(pos)
translate(-thefontsize/3, 0)
fontsize(thefontsize)
fontface(font)
sethue("gray40")
setline(0.5)
text(str)
newpath()
textpath(str)
o = pathtopoly()
@layer begin
fontsize(thefontsize/20)
text(font, Point(0, thefontsize/15))
sethue("magenta")
fontface("Cousine-Bold")
n = 0
for i in o
b = BoundingBox(i)
text(string(round(boxaspectratio(b), digits = 4)), midpoint(b...) + (0, n), halign=:center)
n += thefontsize/20
box(b, :stroke)
end
end
end
end
@png begin
fonts = [
"ArialUnicodeMS",
"AvantGarde-Book",
"BauhausLT-Light",
"CircularStd-Book",
"Cousine",
"FuturaLT-Book",
"GillSansMTPro-Book",
"GothamBlack",
"Helvetica",
"KabelLTStd-Light",
"ProximaNova-Bold",
"Times-Roman",
]
tiles = Tiler(800, 400, 3, 4, margin=40)
for (pos, n) in tiles
drawletteraspect(pos, fonts[mod1(n, length(fonts))], 150, "o")
end
end 800 400 "../images/bezier/drawletteraspect.png"
The fonts on your system will be different, of course.
I wondered which fonts used the most circular circles for the letter “o”. This script wanders through all the fonts registered with Fontconfig and stores the bounding boxes of the letter “o”, then finds “the most circular”.
using Fontconfig, Luxor, DataFrames
function buildfontlist()
fonts = []
for font in Fontconfig.list()
families = Fontconfig.format(font, "%{family}")
for family in split(families, ",")
push!(fonts, family)
end
end
filter!(font -> !startswith(font, "."), fonts)
filter!(font -> !occursin(r"Wing.*", font), fonts)
filter!(font -> !occursin(r".*Extra", font), fonts)
filter!(font -> !occursin(r".*Expert", font), fonts)
return sort(unique(fonts))
end
mutable struct Score
fontname::String
bbox1::BoundingBox
bbox2::BoundingBox
end
scores = Score[]
# we have to have a drawing open, otherwise textpath() doesn't know where to turn
Drawing(100, 100, "/tmp/emptydrawing.png")
fontlist = unique(buildfontlist())
fontsize(20)
for font in fontlist
fontface(font)
newpath() # !!!!
textpath("o")
o = pathtopoly()
bboxes = BoundingBox[]
for i in o
push!(bboxes, BoundingBox(i))
end
if length(bboxes) == 2
push!(scores, Score(font, bboxes[1], bboxes[2]))
end
end
finish() # unused drawing
To analyse the results, we'll put everything into a DataFrame.
using Statistics
df = DataFrame(
Fontname = String[], # font name
Outer = Float64[], # outer aspect ratio
Inner = Float64[], # inner loop aspect ratio
Total = Float64[],
Mean = Float64[]
)
for f in scores
push!(df, [f.fontname,
boxaspectratio(f.bbox1),
boxaspectratio(f.bbox2),
1 - (boxaspectratio(f.bbox1) + boxaspectratio(f.bbox2))/2,
mean([boxaspectratio(f.bbox1),
boxaspectratio(f.bbox2)])
])
end
sort!(df, :Total, lt = (a, b) -> abs(a) < abs(b))
1018×5 DataFrames.DataFrame
│ Row │ Fontname │ Outer │ Inner │ Total │ Mean │
├──────┼─────────────────────────┼──────────┼─────────┼──────────────┼──────────┤
│ 1 │ Kabel LT Std Light │ 1.0 │ 1.0 │ 0.0 │ 1.0 │
│ 2 │ ITC Avant Garde Std XLt │ 0.9965 │ 1.0035 │ -2.04211e-7 │ 1.0 │
│ 3 │ Kabel LT Std Black │ 1.0 │ 1.00101 │ -0.000503525 │ 1.0005 │
│ 4 │ Gill Sans │ 0.972742 │ 1.02949 │ -0.00111546 │ 1.00112 │
│ 5 │ Gill Sans MT Pro Medium │ 0.972742 │ 1.02949 │ -0.00111546 │ 1.00112 │
│ 6 │ ITC Avant Garde Gothic │ 0.993158 │ 1.01428 │ -0.00371842 │ 1.00372 │
│ 7 │ ITC Avant Garde Std Bk │ 0.993158 │ 1.01428 │ -0.00371842 │ 1.00372 │
│ 8 │ ITC Avant Garde Std Md │ 0.991246 │ 1.021 │ -0.00612158 │ 1.00612 │
│ 9 │ Avenir Heavy │ 0.965208 │ 1.02078 │ 0.00700837 │ 0.992992 │
⋮
│ 1009 │ Akzidenz-Grotesk BQ Con │ 2.30537 │ 5.19277 │ -2.74907 │ 3.74907 │
│ 1010 │ AmplitudeComp-Ultra │ 1.52721 │ 6.41949 │ -2.97335 │ 3.97335 │
│ 1011 │ Industria LT Std Solid │ 2.62973 │ 5.72823 │ -3.17898 │ 4.17898 │
│ 1012 │ Bernard MT Condensed │ 1.5877 │ 7.55733 │ -3.57252 │ 4.57252 │
│ 1013 │ Impact │ 1.4851 │ 7.6678 │ -3.57645 │ 4.57645 │
│ 1014 │ HeadLineA │ 1.6 │ 8.34783 │ -3.97391 │ 4.97391 │
│ 1015 │ Univers LT Std 39 Thin │ 3.69307 │ 6.81657 │ -4.25482 │ 5.25482 │
│ 1016 │ Helvetica LT Std ExtCom │ 1.9595 │ 8.82251 │ -4.39101 │ 5.39101 │
│ 1017 │ Gill Sans MT Pro Bold E │ 2.70499 │ 9.1913 │ -4.94815 │ 5.94815 │
│ 1018 │ Helvetica LT Std UltCom │ 2.46103 │ 9.71489 │ -5.08796 │ 6.08796 │
Few typefaces are perfectly circular. Even Circular, LineTo's trendy geometric sans typeface, isn't perfectly circular.
Most of the least circular ones, according to this rough examination, are in the Condensed and Compressed sections of the font libraries. No surprise, Sherlock.
display(df[end-10:end, :])
11×5 DataFrames.DataFrame
│ Row │ Fontname │ Outer │ Inner │ Total │ Mean │
├─────┼────────────────────────────────┼─────────┼─────────┼──────────┼─────────┤
│ 1 │ Helvetica LT Std Compressed │ 5.72485 │ 1.53453 │ -2.62969 │ 3.62969 │
│ 2 │ Akzidenz-Grotesk BQ Condensed │ 2.30537 │ 5.19277 │ -2.74907 │ 3.74907 │
│ 3 │ AmplitudeComp-Ultra │ 1.52721 │ 6.41949 │ -2.97335 │ 3.97335 │
│ 4 │ Industria LT Std Solid │ 2.62973 │ 5.72823 │ -3.17898 │ 4.17898 │
│ 5 │ Bernard MT Condensed │ 1.5877 │ 7.55733 │ -3.57252 │ 4.57252 │
│ 6 │ Impact │ 1.4851 │ 7.6678 │ -3.57645 │ 4.57645 │
│ 7 │ HeadLineA │ 1.6 │ 8.34783 │ -3.97391 │ 4.97391 │
│ 8 │ Univers LT Std 39 Thin UltraC │ 3.69307 │ 6.81657 │ -4.25482 │ 5.25482 │
│ 9 │ Helvetica LT Std ExtCompresse │ 1.9595 │ 8.82251 │ -4.39101 │ 5.39101 │
│ 10 │ Gill Sans MT Pro Bold ExtCond │ 2.70499 │ 9.1913 │ -4.94815 │ 5.94815 │
│ 11 │ Helvetica LT Std UltCompresse │ 2.46103 │ 9.71489 │ -5.08796 │ 6.08796 │
t = Table(2, 5, 170, 140)
@png begin
for (n, f) in enumerate(df[end-9:end, 1])
fontsize(60)
fontface(f)
text("O", t[n], halign=:center)
fontsize(14)
text(f, t[n] + (0, 40), halign=:center)
end
end 800 250 "../images/bezier/leastcircularfonts.png"
The most circular ones on my computer are the classic geometric sans serif fonts.
display(df[1:10, :])
10×5 DataFrames.DataFrame
│ Row │ Fontname │ Outer │ Inner │ Total │ Mean │
├─────┼──────────────────────────┼──────────┼─────────┼──────────────┼──────────┤
│ 1 │ Kabel LT Std Light │ 1.0 │ 1.0 │ 0.0 │ 1.0 │
│ 2 │ ITC Avant Garde Std XLt │ 0.9965 │ 1.0035 │ -2.04211e-7 │ 1.0 │
│ 3 │ Kabel LT Std Black │ 1.0 │ 1.00101 │ -0.000503525 │ 1.0005 │
│ 4 │ Gill Sans │ 0.972742 │ 1.02949 │ -0.00111546 │ 1.00112 │
│ 5 │ Gill Sans MT Pro Medium │ 0.972742 │ 1.02949 │ -0.00111546 │ 1.00112 │
│ 6 │ ITC Avant Garde Gothic S │ 0.993158 │ 1.01428 │ -0.00371842 │ 1.00372 │
│ 7 │ ITC Avant Garde Std Bk │ 0.993158 │ 1.01428 │ -0.00371842 │ 1.00372 │
│ 8 │ ITC Avant Garde Std Md │ 0.991246 │ 1.021 │ -0.00612158 │ 1.00612 │
│ 9 │ Avenir Heavy │ 0.965208 │ 1.02078 │ 0.00700837 │ 0.992992 │
│ 10 │ DIN 30640 Std │ 0.966066 │ 1.01366 │ 0.010139 │ 0.989861 │
t = Table(2, 5, 160, 140)
@png begin
for (n, f) in enumerate(df[1:10, 1])
fontsize(50)
fontface(f)
text("O", t[n], halign=:center)
fontsize(11)
text(f, t[n] + (0, 50), halign=:center)
end
end 800 300 "../images/bezier/mostcircularfonts.png"
In the ‘most circular o’ fonts, there are a few examples from Rudolf Koch's Kabel family.
kabel = df[[occursin(r".*Kabel.*", fontname) for fontname in df[:Fontname]], :]
9×5 DataFrames.DataFrame
│ Row │ Fontname │ Outer │ Inner │ Total │ Mean │
├─────┼──────────────────────┼──────────┼─────────┼──────────────┼──────────┤
│ 1 │ Kabel LT Std Light │ 1.0 │ 1.0 │ 0.0 │ 1.0 │
│ 2 │ Kabel LT Std Black │ 1.0 │ 1.00101 │ -0.000503525 │ 1.0005 │
│ 3 │ ITC Kabel Std Medium │ 0.976604 │ 0.99728 │ 0.0130582 │ 0.986942 │
│ 4 │ Kabel LT Std │ 1.02945 │ 1.0 │ -0.014727 │ 1.01473 │
│ 5 │ Kabel LT Std Book │ 1.02945 │ 1.0 │ -0.014727 │ 1.01473 │
│ 6 │ ITC Kabel Std Ultra │ 0.961288 │ 1.07731 │ -0.0193006 │ 1.0193 │
│ 7 │ ITC Kabel Std │ 1.02357 │ 1.02993 │ -0.0267482 │ 1.02675 │
│ 8 │ ITC Kabel Std Book │ 1.02357 │ 1.02993 │ -0.0267482 │ 1.02675 │
│ 9 │ ITCKabel │ 1.02357 │ 1.02993 │ -0.0267482 │ 1.02675 │
t = Table(2, 5, 160, 140)
@png begin
for (n, f) in enumerate(kabel[:, 1])
fontsize(50)
fontface(f)
text("O", t[n], halign=:center)
fontsize(11)
text(f, t[n] + (0, 50), halign=:center)
end
end 800 300 "../images/bezier/kabelfonts.png"
Here's Wikipedia:
Kabel belongs to the "geometric" style of sans-serifs, which was becoming popular in Germany at the time of Kabel's creation [1920s]. Based loosely on the structure of the circle and straight lines, it nonetheless applies a number of unusual design decisions, such as a delicately low x-height (although larger in the bold weight), a quirky tilted 'e' and irregularly angled terminals, to add delicacy and an irregularity suggesting stylish calligraphy, of which Koch was an expert.
Eye magazine isn't convinced by the apparent geometrical precision of Kabel:
its eccentricities reveal Koch’s unwavering expressionistic and humanist instincts
But at least the ‘o’s are circular...
Moving back to Bézier curves, you can summarize the behaviour of a Bézier curve at a point by finding the curvature. The Luxor function beziercurvature()
returns a number, called kappa
, that varies and flips from positive to negative as the Bézier path varies in ‘curviness’. We're using the first and second derivatives to find the kappa value:
The following drawcurvature()
function uses the kappa value to work out the slope, and draw a perpendicular to the curve, with lengths varying according to the value of kappa, indicating the way the curvature changes.
(This is another kappa, by the way, no relation to the Bézier circularity kappa. Or indeed to the Lancia Kappa...
...or to many of the other things called kappa, such as the curvature of the universe, the torsional constant of an oscillator, Einstein's constant of gravitation, the coupling coefficient in magnetostatics—and that's just in physics.)
function drawcurvature(bezpathsegment::BezierPathSegment;
scale=500,
stepby=0.01,
colors=["purple", "green"])
p1, cp1, cp2, p2 = bezpathsegment
for u in 0:stepby:1
pt = bezier(u, p1, cp1, cp2, p2)
kappa = beziercurvature(u, p1, cp1, cp2, p2)
# comb lines are perpendiculars
@layer begin
pt1 = bezier′(u, p1, cp1, cp2, p2)
# slope of curve at t = y′(t)/x′(t)
# normal is 1/slope
s = -1/(pt1.y/pt1.x)
sign(kappa) >= 0 ? sethue(colors[1]) : sethue(colors[2])
len = scale * abs(kappa)
line(pt - polar(len, atan(s)), pt + polar(len, atan(s)), :stroke)
end
end
end
Here it is in action:
@png begin
p1 = Point(-150, 0)
cp1 = Point(-50, -200)
cp2 = Point(150, 200)
p2 = Point(250, -150)
setline(0.1)
for u in 0:0.025:1
pt = bezier(u, p1, cp1, cp2, p2)
circle(pt, 2, :fill)
end
sethue("red")
circle.([p1, cp1, cp2, p2], 3, :fill)
setline(1)
line(p1, cp1, :stroke)
line(p2, cp2, :stroke)
sethue("black")
fontsize(6)
setline(0.5)
drawcurvature(BezierPathSegment(p1, cp1, cp2, p2),
scale=1500,
stepby=0.005)
end 600 500 "../images/bezier/drawcurvaturecomb.png"
Users of CAD systems (particularly industrial designers) like to display these curvature combs (in both 2D and 3D) to make sure that their shapes don't introduce noticeably abrupt (and possible weak) transitions. Type designers use them too, but as usual they like to let their eyes have the final say.
@png begin
background("gray30")
fontface("Times-BoldItalic")
translate(-250, 80)
fontsize(300)
textpath("julia")
bpaths = pathtobezierpaths()
sethue("gray60")
for bpath in bpaths
for bpseg in bpath
setline(0.5)
drawbezierpath(bpseg, :stroke)
setline(0.25)
drawcurvature(bpseg,
scale=180,
stepby=0.05,
colors=["gold", "cyan"])
end
end
end 800 500 "../images/bezier/typecomb.png"
The dots over the ‘i’ and ‘j’ don't look like perfect circles; these are defined by 8 points, not 4, for some reason.
The value of kappa is typically very small, so the radius of curvature, which is defined as
\[\frac{1}{kappa}\], can become very large. This radius value defines a circle that just touches the curve and follows the curvature at that point. Mathematicians, in typically romantic mood, call it the osculating circle, osculate being from the Latin noun osculum, meaning "kiss".
Drawing osculating circles can be a challenge; they grow very large when the curve looks flat. To be honest it's a tricky diagram to style up, and there's a lot of information that it would be cool to keep and a shame to throw away. There's quite a bit of osculation going on here...
@png begin
p1 = Point(-400, 60)
cp1 = Point(-300, -400)
cp2 = Point(250, 300)
p2 = Point(200, -100)
background("pink")
scale(0.75)
for u in 0:0.001:1
pt = bezier(u, p1, cp1, cp2, p2)
circle(pt, 4, :fill)
end
sethue("red")
circle.([p1, cp1, cp2, p2], 5, :fill)
setline(2)
line(p1, cp1, :stroke)
line(p2, cp2, :stroke)
setopacity(0.1) ; setline(1.5) ; fontsize(5)
for pointofcontact in 0.0:0.015:1.0
kappa = beziercurvature(pointofcontact, p1, cp1, cp2, p2)
pt = bezier(pointofcontact, p1, cp1, cp2, p2)
@layer begin
setopacity(1.0)
sethue("black")
circle(pt, 5, :fill)
end
pt1 = bezier′(pointofcontact, p1, cp1, cp2, p2)
s = -1/(pt1.y/pt1.x) # normal is 1/slope
radius = abs(1/kappa)
centrepoint = pt + polar(radius, atan(s))
if radius < 600
sign(kappa) >= 0 ? sethue("plum1") : sethue("mediumpurple")
circle(centrepoint, radius, :fill)
@layer begin
setopacity(1)
setline(0.25)
sethue("black")
fontsize(12)
circle(centrepoint, radius, :stroke)
circle(centrepoint, 2, :fill)
line(centrepoint, pt, :stroke)
text(string(round(1/kappa, digits = 2)), centrepoint)
end
else
text("too big", pt)
end
end
end 800 800 "../images/bezier/osculate.png"
Bézier curves became popular because they allow us to specify gently curving shapes that would be impractical and cumbersome to specify with circular arcs. The Comprehensive Taxonomy of Irregular Amoeboid Shapes is perhaps still waiting to be written, but here's my contribution to the ‘random ink blot’ chapter. Sometimes you'll get lucky and get a nice one.
function blot(;pos=O, radius=50, npoints=10, action=:fill)
center = pos
pts = ngon(pos, radius, npoints, vertices=true)
for i in 1:length(pts)
pts[i] = pts[i] + (rand(-radius/3:radius/3), rand(-radius/3:radius/3))
end
bezpath = makebezierpath(pts)
for n in 1:length(bezpath)
bezseg = bezpath[mod1(n, length(bezpath))]
if isodd(n)
j = rand(1.4:0.1:7)
bezseg.cp1 = between(center, bezseg.p1, j)
bezseg.cp2 = between(center, bezseg.p2, j)
else
j = rand(0.25:0.1:0.7)
bezseg.cp1 = between(center, bezseg.p1, j)
bezseg.cp2 = between(center, bezseg.p2, j)
end
end
drawbezierpath(bezpath, action)
return bezpath
end
@png begin
sethue("midnightblue")
blot(npoints=10)
circle.([Point(rand(-200:200), rand(-200:200)) for i in 1:15], rand(1:0.1:15, 15), :fill)
end 800 500 "../images/bezier/blot.png"
The control handles of alternate points are positioned on a line from the center to the point.
You could analyse the curvature of these blobs, if you really wanted to, using the drawcurvature()
function from earlier:
@png begin
pl = box(BoundingBox(), vertices=true)
mesh1 = mesh(pl, ["purple", "blue", "yellow", "orange"])
setmesh(mesh1)
paint()
setopacity(0.8)
sethue("orange")
setline(5)
b = blot(radius=80, npoints=8, action=:stroke)
drawbezierpath(b, :fill)
setline(.25)
drawcurvature.(b, colors=["white", "black"])
end 800 500 "../images/bezier/blobcomb.png"
Not all lines generated by computers have to be rigidly straight and precise. What if we could easily make graphics that are a bit more relaxed in style, rather than the rigid CAD-like (and yes, awesome) precision graphics we're used to?
@png begin
t = Table(1, 3)
box(t[1], 160, 160, :stroke)
boxv = box(t[3], 160, 160, vertices=true)
foreach((f, t) ->
brush(f, t, 3),
boxv[1:4],
boxv[mod1.(2:5, 4)])
end 800 300 "../images/bezier/lines.png"
The idea here is that a single line between two points is replaced with some BezierPathSegments that together define a shape that can vary in thickness along its length. This shape can be then filled, and is independent of the set line thickness.
@png begin
setline(60) # <— pointless
brush(O - (250, 0), O + (250, 0), 4, strokes=1)
end 800 50 "../images/bezier/brushline1.png"
The experimental brush()
function is bristling with built-in randomness, so you never know what you're going to get. There are some control knobs available which you can play with.
@png begin
brush(O - (250, 0), O + (250, 0), 5,
strokes=15,
twist=4)
end 800 50 "../images/bezier/brushline2.png"
To be honest, I think it's a bit daft to abandon the machine-like precision that our graphics software usually gives us for this variable hand-made look.
using IterTools
@png begin
background("gray20")
sethue("ivory")
for ss in subsets(ngon(O, 250, 13, vertices=true), 2)
# line(ss[1], ss[2], :stroke) # use this line for accurate graphics
brush(ss[1], ss[2], 4,
strokes=1,
minwidth=0.001,
maxwidth=0.05,
lowhandle=0.1,
highhandle=.2,
randomopacity=true)
end
end 800 600 "../images/bezier/linepartition.png"
Today, our relationship with mechanical production and product design is inconsistent; some of the things we desire we want to be hand-made, but others we'd prefer to be machine-made. Only the most expensive cars claim to be made ‘by hand’; the cheaper models flaunt their nanometre precision instead. Hipsters seek the authentic analogue roughness of the products of the Second Industrial age, but are secretly grateful for and rely on the smooth precisions afforded by the Third. Handmade shoes yes, handmode iPhones, no.
There are a number of plotting packages that offer a hand-drawn aesthetic—this site is web-based. So you can definitely announce your latest scientific discovery using XKCD-style presentation graphics. There are XKCD-styling kits for most of the software used by people who have heard of XKCD.
@png begin
background("bisque")
sethue("gray60")
setopacity(0.1)
blot(pos=O + (-10, -50), radius=100)
sethue("springgreen2")
brush(O + (20, 270), O + (0, -270), 3) # wonk y-axis
brush(O - (350, 0), O + (350, 10), 3) # wonk x-axis
sine = [(Point(50x, -200sin(x)), Point(50(x + pi/6), -200sin(x + pi/6))) for x in -2pi:pi/10:2pi]
sethue("pink1")
brush(Point(-300, -200), Point(300, -200), 0.5)
sethue("mediumorchid")
foreach(pr -> brush(pr[1], pr[2], twist=5, highhandle=4, strokes=3), sine)
end 800 600 "../images/bezier/graph.png"
Unfortunately you've got the imprecise finish without the reassuring and lovable hand-made quirks. Like those imitation hand-writing fonts, it's presenting the illusion of manual labour.
@png begin
fontsize(30)
fontface("SnellRoundhand-Script")
text("pint of milk cat food stamps something for dinner? ", halign=:center)
end 800 100 "../images/bezier/shoppinglist.png"
But it's always fun to explore an idea to see where it leads:
@png begin
t = Table(5, 5, 130, 100)
l = t.colwidths[1]
fontsize(4)
for (pos, n) in t
weight = rand(0:2:20)
strokes = rand(1:20)
minwidth=rand(0.0:0.1:0.3)
maxwidth=rand(minwidth+0.1:0.1:minwidth+0.3)
twist = rand(-10:10)
randomopacity = rand(Bool)
lowhandle = rand(0.0:0.1:0.5)
highhandle = rand(lowhandle:0.1:lowhandle+.5)
tidystart = rand(Bool)
@layer begin
translate(pos)
rotate(-0.2)
randomhue()
brush(O - (l/2, 0), O + (l/2, 0), weight,
strokes=strokes,
minwidth=minwidth,
maxwidth=maxwidth,
lowhandle=lowhandle,
highhandle=highhandle,
twist=twist,
randomopacity=randomopacity,
tidystart=tidystart)
end
textwrap("weight: $weight
strokes: $strokes
minwidth: $minwidth
maxwidth $maxwidth
twist: $twist
randomopacity: $randomopacity
tidystart :$tidystart", t.colwidths[1] - 30, pos + (-l/2, 30))
end
sethue("black")
fontsize(30)
fontface("Elephant")
text("brush strokes", boxtop(BoundingBox()) + (0, 40), halign=:center)
end 800 600 "../images/bezier/brushes.png"
The line quality can make for simple painterly graphics, good for the occasional Bob Ross painting:
using ColorSchemes
function drawsunset()
colscheme= ColorSchemes.sunset
@png begin
background("burlywood4")
sethue("orange")
circle(O + (0, 60), 60, :fill)
setopacity(0.5)
for y in -200:20:180
sethue(get(colscheme, rescale(y, -200, 180, 1, 0)))
for w in 10:5:20
brush(Point(-350, y), Point(350, y), w,
strokes = 1,
twist = 5,
minwidth=0.02,
maxwidth=0.04,
randomopacity=true
)
end
end
fontsize(30)
fontface("Pique-Black")
text("Sunset over Luxor", Point(0, 230), halign=:center)
end 800 500 "../images/bezier/sunset.png"
end
drawsunset()
Each time you evaluate this the result is slightly different, yet always the same. Perhaps the next one will be better — wait, no, perhaps I preferred the previous one...
And because we started in France, let's paint some graffiti:
function drawfrenchflag()
w, h = 700, 400
@png begin
background("azure")
setopacity(0.85)
setline(0.5)
colscheme = ColorScheme([Colors.RGB.(sethue("royalblue1")...),
Colors.RGB.(sethue("white")...),
Colors.RGB.(sethue("red")...)])
for x in -w/2:40:w/2
# reduce x to 1, 2, or 3, then to [0-1] to get red/white/blue
col = rescale(div(x+w/2, w/3) + 1, 1, 3, 0, 1)
sethue(get(colscheme, col))
for i in 5:10:50
brush(Point(x, -h/2), Point(x, h/2),
i,
strokes=2,
twist=10,
highhandle=1)
end
end
b = box(O, 675, 375, vertices=true)
for i in 1:length(b)
sethue("purple")
brush(b[i], b[mod1(i + 1, length(b))], 30,
strokes=20,
minwidth=0.01,
maxwidth=0.05)
end
fontface("MistralStd")
setopacity(0.7)
sethue("purple")
fontsize(100)
text("Vive la France!", O + (10, 80), halign=:center, angle=-pi/10)
end 800 500 "../images/bezier/frenchflag.png"
end
drawfrenchflag()
...and then speed off in our curvaceous old Citroen DS and drive to the France/Swiss border, where CERN are busy recreating the big bang:
using ColorSchemes
function unpetitbang()
@png begin
background("gray10")
# rays
for i in 1:300
sethue(get(ColorSchemes.inferno, rand()))
rotate(rand())
brush(O + (rand(30:500), 0), O + (rand(501:1000), 0), 1,
strokes=5,
twist=rand(-1:0.1:1),
lowhandle = 0.0,
highhandle = 0.5
)
l = rand(100:600)
l1 = l + rand(5:50)
sethue("white")
brush(O + (l, 0), O + (l1, 0), 1,
strokes=10,
twist=rand(-1:0.1:1),
lowhandle = 0.0,
highhandle = 1
)
end
# plasmas
for i in 1:20
sethue(get(ColorSchemes.plasma, rand()))
setopacity(rand(0.25:.05:0.75))
rotate(rand())
brush(O - (20, 0), O + (20, 0), 20,
maxwidth = 10,
highhandle = 10)
end
# orbits
sethue("ivory")
setline(0.5)
for i in 1:10
rotate(rand())
brush(O - (0, 0), O + (50, 0), 10,
maxwidth = 20,
highhandle = 20,
action=:stroke)
end
end 1000 1000 "../images/bezier/unpetitbang.png"
end
unpetitbang()
[2018-06-20]
| [1] | Updates |
I've updated this post to work with Julia v1.0 and to take advantage of Franklin's ever-growing feature set.
| [2] | Graphic formats |
All the images in this post are in PNG, but they looked better in the vector-based SVG format. However, there was an annoying ‘bug’ in Jupyter/IJulia/IPython involving text in SVG images created by Cairo. What happened was that Cairo tried to be smart and stored text in XML symbols, suitable for re-use. A good idea, but unfortunately they're stored in the notebook's ‘global XML scope’, and so later cells accidentally picked up symbol definitions from earlier cells and re-used them, even though that's not always what you wanted.
| [3] | Don Lancaster |
Don Lancaster is the totally awesome dude who was active in the very early days of personal computing, and probably knows more about PostScript than most of the current Adobe Systems employees put together. Travel back in time by visiting his wacky website at http://www.tinaja.com.
This page was generated using Literate.jl.


No selection.
Toggle the proteins between a bar and a circle
Click on protein
Zoom
Mouse wheel
Pan
Click and drag on background
Move protein
Click and drag on protein
Expand bar
(increases bar length until sequence is visible)
Shift_left-click on protein
Rotate bar
Click and drag on handles that appear at end of bar
Hide/show protein (and all links to it)
Right-click on protein
Hide links between two specific proteins
Right click on any link between those proteins
Show all hidden links
Right click on background
'Flip' self-links
Right-click on self-link
Demo
Reset
Export SVG
Legend
Details
Help
Self-Links
Ambig.
Decoys
(cut-off)
Annot.
Custom
UniprotKB
SuperFamily
Lysines
None


Since times immemorial, our elders have talked about the necessity to purchase a house. A shelter over one’s head has been a quest we all have fought for since centuries. Since the late 1900s and early 2000s Real Estate prices had started picking up or even sky-rocketing in various parts of the country to an extent that post 2010s buying a house has become a dream for many. For all those who own properties solely for the purpose of trading / investment, there has been a massive slowdown in the last few years. This has led many to question whether the Real Estate story, which had a fairy tale beginning, has come to an end or not. Let’s take a look.
As seen from the chart below, since 2001 the real estate market was in an initial growth phase with new metro cities such as Bangalore, New Bombay and Gurgaon (now rechristened as Gurugram) being formed to cater to the growing demand for urban cities. I distinctly remember various people who had invested in properties at the start of this cycle being the happiest given that housing prices soon started booming all over the country by as much as 4 fold in as many years. Between 2006 and 2008 price growth in real estate flew off the charts with many areas suddenly becoming unaffordable for the most of the middle class people.
Source: Crisil Research
Then happened the 2008 global market crash which shook not just equities and bonds but almost all sectors across the globe. There was a sudden flux in prices which was caused by the real estate market crash in the US. Demand fell and so did prices as many saw a fall in confidence of the investors. Post 2010, there was consolidation in the market with demand and prices picking up albeit slowly given the improving Indian outlook in the global market.
Cut to 2016, various steps are being taken by the government to propel India to become the fastest growing nation. With initiatives such as housing for all and curb on black money, many have started expecting market prices to fall anywhere between 30% - 50% in the coming months due to Real Estate bubble having formed in the last 2 decades. However, let’s try and understand whether this actually happens or not.
As can be seen from the above chart, there continues to be a growing shortage of housing in urban areas since 2001 with the peak being more the 21% in 2014. Despite the government’s ambitious plan of building around 20 smart cities, many feel that it would take more than a decade or so to achieve this massive target. This would mean that the prices would continue their upward trend at the current pace for the next few years. Also the increasing population and ageing current metro cities would mean an investment to upgrade the existing infrastructure in the coming years.
Although we don’t expect the prices to boom in the coming years, but neither do we see a fall as speculated by some. So even though many have missed the bus in the past, there is still scope for them to invest and reap the benefits in the coming years.


James
|
Garloff
Web Development
Available for: Permanent Hire and Freelance Work
Find Out More
Built with:
AngularJS,
HTML5 / CSS3 / Bootstrap4,
JavaScript (ECMA6)
*Note: Site is undergoing upgrade to Angular 6
Demos of Personal Projects
Css Grid Example
Login Authentication
Data Grid


Trainers Journey
Building an Online Presence as a Personal Trainer
This article goes in-depth into the first stop along the way of becoming a successful online personal trainer. Your website. ...
Earn trophies by discovering new locations before your friends do. It's more fun when it's social.
Earn trophies by discovering new locations before your friends do. It's more fun when it's social.
Earn trophies by discovering new locations before your friends do. It's more fun when it's social.


This page documents that roadmap and gives you an idea of what to expect from future versions and timeframes for release.
Our users keep inventing better ways and adding new requirements. The downside is that our todo list keeps growing. The upside is that there is plenty of work to go around. If you are interested in participating, send an email on the log4j-dev@ mailing list stating your interest. You'll be promptly enrolled. We're always looking for help! Don't be put off if in the "Volunteer" column already has a person listed. Programming is fun, especially if it is done in a team.
Expected timeframe: As needed
log4j 1.2.14 was released in September 2006. log4j 1.2 is continuing to be maintained in response to reported, but no active development is anticipated in the near future. Backporting the org.apache.log4j.rolling package from log4j 1.3 might be desirable since usage problems with the original org.apache.log4j.RollingFileAppender and org.apache.log4j.DailyRollingFileAppender are commonly reported.
Expected timeframe: indeterminate
Compatibility with earlier releases (as implied by a minor verion number change) was not enforced during the development process and much of the recent effort has been to restore compatibility. However, it does not seem likely that log4j 1.3 will ever been sufficiently compatible with log4j 1.2 to recommend it as a general replacement for log4j 1.2. Additional alpha releases are expected, however it is possible that the development focus may shift to log4j 2.0.
Expected timeframe: indeterminate
log4j 2.0 is still conceptual but would be designed for Java 5 and later, would use be designed for fine-grain concurrency to maximize performance on multi-processor systems, would minimize exposure of implementation details and adhere to current Java coding practices. The design of log4j 2.0 may attempt to support javax.util.logging by allowing, for example, a log4j 2.0 Appender to serve as a javax.util.logging.Handler. log4j 2.0 would likely be modularized, possibly using OSGi and would include a facade that emulates the log4j 1.3 API but would not be compatible with user written appenders and other components.


A Step in the right direction.
Componentized software to improve development efficiency.
NameParser
A name parsing library that parses name strings using Western-English naming conventions for human names and identifies organizational names.
Essentials
A .NET Library With Utility Classes, Methods and common functionality to be used across multiple projects and project types.


{include file="common/meta" /}
{include file='common/header' /}
{if $fixedmenu}
{/if} {if $referermenu}
{/if}
{include file="common/control" /}
{include file="common/script" /}


Warehouse Keeper Dorankus:
We guard your property with our lives -- you have my word! Why not deposit your valuables with us and sleep easier tonight?
Private Warehouse
Clan Warehouse
Quest


Between 2001 and 2010, Florida’s refusal to regulate pill mills created a pipeline of illicit oxycodone and other pills to users and traffickers across the United States.
In the first half of 2010, drug distributors shipped 40.8 million oxycodone pills to Florida doctors.
Every other state combined got only 4.5 million.
Florida pill mills sold the oxycodone to users and traffickers from other states.
Other states cracked down on their pill mill problems.
Florida didn't.
As long as Florida pill mills stayed open, oxycodone deaths kept rising, here and in other states.
In 2010, The Drug Enforcement Administration reported Florida pill mills were spreading oxycodone in the Southeast, Great Lakes, Northeast and Middle Atlantic regions - virtually every state east of the Mississippi.
A national oxycodone ring even bought South Florida pills to sell in Denver, Salt Lake City, Washington, Oregon, Texas and Alaska.
In 2011, after years of ignoring the crisis, Florida started arresting doctors, cracking down on bogus clinics and tracking oxycodone and other opioid prescriptions.
Oxycodone became harder to get. People began switching to a much cheaper, easier-to-get and equally powerful substitute:
Why would people switch from a prescription drug to a black-market street drug?
The molecular structure of oxycodone and heroin are nearly identical. So is their high.
East of the Mississippi illicit Florida oxycontin had flowed freely. Now, heroin death rates rose sharply as people switched drugs.
West of the Mississippi, Florida oxycodone was not as common. Heroin deaths dropped.
But in seven states where Florida oxycodone had been trafficked, heroin deaths rose by a collective 27 percent
Dig deeper and a more patterns emerge
There was only one region in the entire U.S. where Florida oxycodone was not known to have been trafficked at all: the Great Plains states. And it was the only region in the U.S. where heroin deaths dropped
Feel free to leave us a comment on the Grayscale template overview page on Start Bootstrap to give some feedback about this theme!


Instant door. Note, that it is easier to just use flag 1 bit 2 on a sector for an instant door. Elevator door is only really needed if you want to alter variables, for example, create a key door.
Stops and event_mask are set automatically - just make sure that the ceiling altitude of the sector is when the door is OPEN.


Labour's Corbyn says Trump got his wish of Johnson-Farage deal
LONDON (Reuters) - The leader of Britain’s opposition Labour Party said on Monday that U.S. President Donald Trump had got his wish for Brexit Party leader Nigel Farage to make a pact with Prime Minister Boris Johnson.
Farage said he would not stand candidates against Johnson’s Conservatives in 317 seats the party won in 2017.
“One week ago Donald Trump told Nigel Farage to make a pact with Boris Johnson,” Labour leader Jeremy Corbyn said on Twitter. “Today, Trump got his wish.”
“This Trump alliance is Thatcherism on steroids and could send 500 million pounds a week from our NHS to big drugs companies. It must be stopped.”


--- date: 2010-03-02T00:00:00Z published: true title: Google Moderator - I had no idea this existed. url: /2010/03/02/google-moderator-i-had-no-idea-this-existed-/ ---
Let your audience decide
Get to know your audience by letting them decide which questions, suggestions or ideas interest them most.Everyone's voice is heard
The voting box at the top of page focuses attention on submissions recently added and on the rise, making it simple and easy to participate.Be creative
Include people in your preparation for lectures, interviews and hard decisions or work together to organize feature requests and brainstorm new ideas.


By using this page, you agree to the use of local storage.
Add a message: m(messageId, timeReady, timeSent, length);
Offset a time label: moveTimeX(time, x); and moveTimeY(time, y);
Add a message color: color(messageId, delayColor, sendColor, textColor);
Time axis settings: timeScale(scale); and timeOrigin(origin);


| Name | Type | Description |
|---|---|---|
| params | object | the parameters for the request. the following keys are valid: apikey (the application API key), secret (the application API secret or null if using a session proxy), sessionProxy (the URL to the application session proxy), style (the button style - which is either normal or wide). |
| parmeters | object | (optional) a dictionary object properties defined in Titanium.Facebook.LoginButton |


Chapter start new page odd page even page
Chapters can be made to always start on an odd page or directly follow the previous page.
Default page count behavior for Bookmaps is to start every chapter on an odd page.
Blank pages mark blank page empty blank page
Blank pages are marked with This page intentionally left blank text.
This page intentionally left blank
Chapter layout chapter TOC no chapter TOC
Chapters can start with a table of chapter contents on a separate chapter cover page.


I am green if javascript is on.
I am blue if your browser supports html5 media elements.
I am red if your browser viewport width > 1000px and it supports canvas.
I should never be yellow.


Plugin Information |
|---|
View Assembla merge request builder on the plugin site for more information. |
Allows Jenkins to build merge requests from Assembla.
This plugin fetches the source and target branches of a Assembla merge request and makes them available to your build via build parameters. Once the build completes, Jenkins will leave a comment on the merge request and related tickets indicating whether the merge request was successful.
Required Jenkins Plugins
Installation
- Ensure that a Jenkins user exists within your Assembla space, has access to the repository and has permissions to leave comments. Ensure that the user has Member level access to the project.
- Install the plugin in Jenkins.
- The plugin is hosted on the Jenkins Plugin repository
- Go to Jenkins -> Manage Plugins -> Available
- Search for Assembla Merge Request Builder
- And install it
- Ensure you restart Jenkins
- Go to Manage Jenkins -> Configure System -> Assembla Merge Request Builder
- Set your User API key key for the Jenkins user. This can be found by logging into Assembla as Jenkins and going to the account page
- Set your User API secret secret for the Jenkins user. This can be found the same way as API key.
- Click Test credentials button
- Set/change any of the other available parameters as necessary.
- Save to preserve your changes.
- Go to Manage Jenkins -> Configure Global Security and set Markup Formatter to Safe HTML. It will make Jenkins display links in build history properly.
Webhooks
- Make sure that you have Webhook Tool installed and you have a git repository in your space. If no, go to Admin -> Tools -> More -> Webhook section and click "Add" button.
- You need to configure the Webhook Tool only once per each space.
- Set Assembla webhook to trigger your Jenkins server. Go tohttps://www.assembla.com/spaces/`your_space_name`/webhooks. Create new webhook, select "Assembla Jenkins plugin" from template (Make sure "Code comments" and "Code commits" are checked in Post updates aboutsection).
Creating a Job
- Create a new job by going to New Job
- Set the Project Name
- In the Source Code Management section:
- Click Git, enter your Repository URL and in Advanced set its Name to origin
- In Branch to build enter ${assemblaSourceRepositoryName}/${assemblaSourceBranch}
- OPTIONAL: For merge requests from forked repositories, add another repository with Repository URL ${assemblaSourceRepositoryUrl} and name ${assemblaSourceRepositoryName}.
- OPTIONAL: You can configure Jenkins to merge source with target branch before build. In the Additional Behaviours section:
- Click the Add drop down button and the Merge before build item
- Specify the name of the repository as origin and enter the Branch to merge to as ${assemblaTargetBranch}
- NOTE: ${assemblaTargetBranch} parameter is not available when using "Push trigger"
- Ensure Prune stale remote-tracking branches is not added
- In the Build Triggers section:
- Check the Assembla Triggers checkbox and Assembla Merge Request Builder configuration section will appear.
- Enter Space name and Repository name and click Check settings button. (For example: If your code browser url is https://www.assembla.com/spaces/pavel-test/git/source, space name will be pavel-test and repo name - git.
- Check Comment and vote merge request to allow Jenkins to post a comment about build result and upvote/downvote merge request
- If you want to be notified when Jenkins starts the build, check Comment after job start checkbox. Jenkins will post a comment to a merge request and related tickets (if Comment related tickets checkbox is checked).
- Configure any other pre build, build or post build actions as necessary
- Save to preserve your changes
Manual Triggers
You can trigger a job a manually by clicking This build is parameterized and adding the relevant build parameters. These include:
- $assemblaSourceRepositoryUrl - assembla repository url
- $assemblaTargetBranch - merge request target branch
- $assemblaSourceBranch - merge request source branch
*Note: A manually triggered build will not add build triggered/succeeded/failed comments to the merge request.
*Note: You should ensure that the 'Global Config user.name Value' and 'Global Config user.email Value' are both set for your git plugin. In some cases, you will get an error indicating that a branch cannot be merged if these are not set.
Message templates
Plugin allows to customize build messages using templates (Manage Jenkins -> Configure System -> Assembla Merge Request Builder). You can use any env variables or build parameters in templates. For example, you can mention your QA team to test changes or mention merge request author about build result etc. Additionally plugin provides:
- $mrTitle - merge request title
- $mrAbbrTitle - merge request title truncated to 30 chars
- $mrUrl - merge request url
- $mrId - merge request id
- $jobName - jenkins job name
- $buildStatus - build status (available only if build is completed)
- $buildUrl - build url
- $assemblaSourceSpaceId - assembla space id
- $assemblaDescription - merge request description
- $assemblaSourceRepositoryUrl - assembla repository url
- $assemblaTargetBranch - merge request target branch
- $assemblaSourceBranch - merge request source branch
- $assemblaMergeRequestId - assembla merge request id
- $assemblaRefName - build git revision
Contributing
- Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
- Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
- Fork the project
- Start a feature/bugfix branch
- Commit and push until you are happy with your contribution
- Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
- Please try not to mess with the version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
Copyright
Copyright (c) 2016 Assembla Inc. See LICENSE for further details.


Samurai
Block Publisher:
Select Sponsored Language:
English
Hebrew
Spanish
French
German
Portugese
Japanese
Chinese
Korean
Dutch
Polish
Italian
Russian
Turkish
Thai
Dannish
Change Language!
Update Branding!
Please provide styling details:
Font Family
Please provide title styling details:
font weight
font color
Please provide description styling details:
font weight
font color
Please provide branding styling details:
font weight
font color
Update CSS stylings!
test text.


Welcome back Janice!
How do you feel today? Ready to get a Moody Drink?
Search
Try It
Favourites
History
{{#each results}}
{{this.name}}
{{#each this.ingredients}}
{{this.measurement}}
{{this.name}}
{{/each}}
Instructions/Steps
{{this.instructions}}
Try Later
Favourite
{{/each}}


This is the blog for the Apache Beam project. This blog contains news and updates for the project.
May 17, 2017 • Davor Bonaci [@BonaciDavor] & Dan Halperin
The Apache Beam community is pleased to announce the availability of version 2.0.0. This is the first stable release of Apache Beam, signifying a statement from the community that it intends to maintain API stability with all releases for the foreseeable future, and making Beam suitable for enterprise deployment.
Mar 16, 2017 • Ahmet Altay
Apache Beam’s latest release, version 0.6.0, introduces a new SDK – this time, for the Python programming language. The Python SDK joins the Java SDK as the second implementation of the Beam programming model.
Feb 13, 2017 • Kenneth Knowles [@KennKnowles]
Beam lets you process unbounded, out-of-order, global-scale data with portable high-level pipelines. Stateful processing is a new feature of the Beam model that expands the capabilities of Beam, unlocking new use cases and new efficiencies. In this post, I will guide you through stateful processing in Beam: how it works, how it fits in with the other features of the Beam model, what you might use it for, and what it looks like in code.
Feb 1, 2017 • Davor Bonaci [@BonaciDavor]
One year ago today Apache Beam was accepted into incubation at the Apache Software Foundation. The community’s work over the past year culminated, just over three weeks ago, with an announcement that Apache Beam has successfully graduated as a new Top-Level Project at the foundation. Graduation sparked an additional interest in the project, from corporate endorsements, news articles, interviews, to the volume of traffic to our website and mailing lists.
Jan 10, 2017 • Davor Bonaci [@BonaciDavor]
Today, the Apache Software Foundation announced that Apache Beam has successfully graduated from incubation, becoming a new Top-Level Project at the foundation and signifying that its “community and products have been well-governed under the foundation’s meritocratic process and principles”.
Jan 9, 2017 • Thomas Weise [@thweise]
The latest release 0.4.0 of Apache Beam adds a new runner for Apache Apex. We are excited to reach this initial milestone and are looking forward to continued collaboration between the Beam and Apex communities to advance the runner.
Oct 20, 2016 • Thomas Groh
The Beam Programming Model unifies writing pipelines for Batch and Streaming pipelines. We’ve recently introduced a new PTransform to write tests for pipelines that will be run over unbounded datasets and must handle out-of-order and delayed data.
Oct 11, 2016 • Jesse Anderson [@jessetanderson]
Tyler Akidau and I gave a three-hour tutorial on Apache Beam at Strata+Hadoop World 2016. We had a plethora of help from our TAs: Kenn Knowles, Reuven Lax, Felipe Hoffa, Slava Chernyak, and Jamie Grier. There were a total of 66 people that attended the session.
Aug 3, 2016 • Frances Perry [@francesjperry]
It’s been just over six months since Apache Beam was formally accepted into incubation with the Apache Software Foundation. As a community, we’ve been hard at work getting Beam off the ground.
Jun 15, 2016 • Davor Bonaci [@BonaciDavor]
I’m happy to announce that Apache Beam has officially released its first version – 0.1.0-incubating. This is an exciting milestone for the project, which joined the Apache Software Foundation and the Apache Incubator earlier this year.
Jun 13, 2016 • Aljoscha Krettek [@aljoscha]
We recently achieved a major milestone by adding support for windowing to the Apache Flink Batch runner. In this post we would like to explain what this means for users of Apache Beam and highlight some of the implementation details.
May 27, 2016 • Robert Bradshaw
Have you ever wondered why Beam has PTransforms for everything instead of having methods on PCollection? Take a look at the history that led to this (and other) design decisions.
May 18, 2016 • Dan Halperin
This morning, Eugene and Malo from the Google Cloud Dataflow team posted No shard left behind: dynamic work rebalancing in Google Cloud Dataflow. This article discusses Cloud Dataflow’s solution to the well-known straggler problem.
Apr 3, 2016 • Frances Perry [@francesjperry] & Tyler Akidau [@takidau]
Are you interested in giving a presentation about Apache Beam? Perhaps you want to talk about Apache Beam at a local Meetup or a convention. Excellent! The Apache Beam community is excited to expand and grow the community. To help kickstart this process, we are excited to announce an initial set of Apache Beam presentation materials which anyone can use to give a presentation about Apache Beam.
Mar 17, 2016 • Frances Perry [@francesjperry] & Tyler Akidau [@takidau]
With initial code drops complete (Dataflow SDK and Runner, Flink Runner, Spark Runner) and expressed interest in runner implementations for Storm, Hadoop, and Gearpump (amongst others), we wanted to start addressing a big question in the Apache Beam (incubating) community: what capabilities will each runner be able to support?
Feb 25, 2016 • James Malone [@chimerasaurus]
When the Apache Beam project proposed entry into the Apache Incubator the proposal included the Dataflow Java SDK. In the long term, however, Apache Beam aims to support SDKs implemented in multiple languages, such as Python.
Feb 22, 2016 • James Malone [@chimerasaurus]
One of the major benefits of Apache Beam is the fact that it unifies both both batch and stream processing into one powerful model. In fact, this unification is so important, the name Beam itself comes from the union of Batch + strEAM = Beam
When the project started, we wanted a logo which was both appealing and visually represented this unification.


Verson 2.0 is coming soon, and yes itll be bigger and better than the last version. Currently in production process, here's what Version 2.0 has to offer:
Faster, smaller and more concise
More Utility Based Components for a vast range of purposes
Version 2.0 is going bigger, Asun is employing cultural utilities to suite vast design tastes for various users
Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatibus corrupti laudantium commodi reiciendis, esse dolore eveniet nobis vero minima atque molestias nisi quibusdam odit, cumque modi? Illum iste numquam veritatis magni qui nihil. Ducimus, quisquam temporibus asperiores illum inventore dignissimos quidem. Illo dolorem aliquam ad dolor a quidem magnam.
Download and Link Asun.min.css to the head of your html file
Install with NPM


{% macro post_card(post=None, user_liked_posts_ids=[]) %}
{{ post.description }}
{% else %}This is the preview of your post card! It will appear on the feed exactly like this, except for the date and time. Click 'Save draft' to save the post, and press 'Publish' to make it visible to other members. This card is rendered as you type in the fields. Happy posting!
{% endif %}


Important C++ Classes Provided By The Qt QML Module
The Qt QML module provides C++ classes which implement the QML framework. Clients can use these classes to interact with the QML run-time (for example, by injecting data or invoking methods on objects), and to instantiate a hierarchy of objects from a QML document. The Qt QML module provides more C++ API than just the classes listed here, however the classes listed here provide the foundations of the QML runtime and the core concepts of QML.
QML Runtime
A typical QML application with a C++ entry-point will instantiate a QQmlEngine and then use a QQmlComponent to load a QML document. The engine provides a default QQmlContext which will be the top-level evaluation context used for evaluating functions and expressions defined in the QML document. The object hierarchy defined in the QML document will be instantiated by calling the create() function of the QQmlComponent instance, assuming that no errors were encountered during document loading.
The client may wish to modify the QQmlContext provided by the engine, by injecting properties or objects into the context. They can call the QQmlEngine::rootContext() function to access the top-level context.
After instantiating the object, the client will usually pass control to the application event loop so that user input events (like mouse-clicks) can be delivered and handled by the application.
Note: The Qt Quick module provides a convenience class, QQuickView, which provides a QML runtime and visual window for displaying a QML application.
The QQmlEngine Class
The QQmlEngine class provides an engine which can manage a hierarchy of objects which is defined in a QML document. It provides a root QML context within which expressions are evaluated, and ensures that properties of objects are updated correctly when required.
A QQmlEngine allows the configuration of global settings that apply to all of the objects it manages; for example, the QNetworkAccessManager to be used for network communications, and the file path to be used for persistent storage.
See the QQmlEngine class documentation for in-depth information about what the QQmlEngine class provides, and how it can be used in an application.
The QQmlContext Class
The QQmlContext class provides a context for object instantiation and expression evaluation. All objects are instantiated in a particular context, and all of the expressions which are evaluated while an application is running are evaluated within a particular context. This context defines how symbols are resolved, and thus which values the expression operates on.
See the QQmlContext class documentation for in-depth information about how to modify the evaluation context of an object by adding or removing properties of a QQmlContext, and how to access the context for an object.
Dynamic Object Instantiation and Expression Evaluation
Dynamic object instantiation and dynamic expression evaluation are both core concepts in QML. QML documents define object types which can be instantiated at run-time using a QQmlComponent. An instance of the QQmlComponent class can be created in C++ directly, or via the Qt.createComponent() function in imperative QML code. Arbitrary expressions can be calculated in C++ via the QQmlExpression class, and such expressions can interact directly with the QML context.
The QQmlComponent Class
The QQmlComponent class can be used to load a QML document. It requires a QQmlEngine in order to instantiate the hierarchy of objects defined in the QML document.
See the QQmlComponent class documentation for in-depth information about how to use QQmlComponent.
The QQmlExpression Class
The QQmlExpression class provides a way for clients to evaluate JavaScript expressions from C++, using a particular QML evaluation context. This allows clients to access QML objects by id, for example. The result of the evaluation is returned as a QVariant, and the conversion rules are defined by the QML engine.
See the QQmlExpression class documentation for in depth information about how to use QQmlExpression in an application.
Usage of the Classes within QML Applications
These pages describe how to create QML applications which interact with the C++ classes:


Dolphins Player Mark Walton Waived After Allegedly Hitting Pregnant Woman
by VladTVRunning back Mark Walton was recently waived by the Miami Dolphins after he was arrested for aggravated assault of a pregnant woman. According to the police report, Walton allegedly punched a woman in her face numerous times after finding out she was pregnant with his child.
The situation reportedly went down at 4:15 am Tuesday, and along with punching the woman, Walton allegedly pushed the woman into a wall. Walton was arrested and taken to Broward County Jail.
Since then, the Dolphins issued a statement on the matter, saying, “We hold our players to a high standard and take these matters very seriously. We will have no further comment at this time." Take a look above.
source: ESPN


Content for tab one here.
Content for tab two here
Content for tab three here
Content for tab four here
The code provides the capability to activate tabs elsewhere (not just by clicking tabs).
More information and documentation is available online.
Please read dyn-web's Terms of Use if you plan to use our code.


Who I am
Hi! My name is Stefan Nicolae Teodoropol and I'm a 32 year old computer science student living in Zürich. Welcome!
What I do
I'm an IT / computer hardware guru looking for a challenge!
After my studies (from Sep. 2023 onwards) I will be interested in working on projects based around Zürich.
When I'm not studying or making music, I really enjoy using multiple online learning platforms. Check out my progress:
- freecodecamp.org
A great place to interactively start learning HTML and CSS.
- codingame.com
Pick your favourite language and start solving challenges against other programmers around the world in real time!
- spoj.com
Sphere online judge is a great site with many classical coding challenges to solve.
How I code
I have a GitHub account - in fact, this site is also hosted on GitHub! Check it out!
I have in-depth knowledge of these technologies:
- Java
- Python
- Rest APIs
- OpenAPI / Swagger
- Bootstrap
- HTML
- JavaScript
- CSS
- Azure
- PowerShell
- Git
- Machine Learning
I am currently learning more about:
- WebAssembly
- AI / Neural Networks
I've also used:
- Angular
- React
- Docker
- Spring Boot
- MySQL
- C#
- Android Studio
Previous IT Work
- Current - Working Student - Zurich Insurance Group Ltd
I am currently studying and working part time in the Investment Applications - Strategy Development team.
- 2019 - Website Developer - Stefan Teodoropol Online
As a first step towards a personal online presence, I have written a simple landing page with my contact info.
- 2018 - DevOps Engineer - Citavi, Swiss Academic Software GmbH
I worked with the developers of Citavi to migrate and integrate their existing Team Foundation Server workflow to a Git-based workflow on top of Microsoft Azure as a development and deployment ecosystem.
- 2009 - Mod Developer - F22 TAW Community Mod Project
I have previously contributed to the F22 TAW Community Mod Project. Currently my NVG Mod is included in the latest 2.30 version of TAW. My contributions are under the name 'Eagle_Flight' and you can check out the Wiki here.


This function imports one or more Sample Design Database[s] and returns a list of the named lists sf, pts, and strata. The named lists contain SpatialPoints/PolygonsDataFrames of the sample frame, points, and strata from each of the geodatabases. The SPDFs are named using the filename of the geodatabase source, so that each list has one SPDF named for each geodatabase imported and those names are identical between lists. If a Sample Design Database is missing any one of those features, a NULL value replaces the SPDF.
read.dd(src = "", dd.src, func = "arcgisbinding", target.values = c("Target Sampled", "TS"), omitNAdesignations = FALSE, projection = CRS("+proj=longlat +datum=NAD83 +no_defs +ellps=GRS80 +towgs84=0,0,0"))
| src | Character string defining the filepath containing the sample design database[s]. |
|---|---|
| dd.src | Character string or character vector containing the filenames of the geodatabases to import. Each filename should include the extension ".gdb" |
| func | Character string. Defines whether to use the |
| projection |
|


| Class and Description |
|---|
| OptimizationAlgorithm
Optimization algorithm to use
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Layer
Interface for a layer of a neural network.
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| ModelAdapter
This interface describes abstraction that uses provided model to convert INDArrays to some specific output
|
| Class and Description |
|---|
| FwdPassType
Type of forward pass to do.
|
| Layer.TrainingMode |
| Layer.Type |
| MaskState
MaskState: specifies whether a mask should be applied or not.
|
| Model
A Model is meant for predicting something from data.
|
| OptimizationAlgorithm
Optimization algorithm to use
|
| Trainable
Trainable: an interface common to Layers and GraphVertices that have trainable parameters
|
| TrainingConfig
Simple interface for the training configuration (updater, L1/L2 values, etc) for trainable layers/vertices.
|
| Class and Description |
|---|
| Classifier
A classifier (this is for supervised learning)
|
| Layer
Interface for a layer of a neural network.
|
| Model
A Model is meant for predicting something from data.
|
| Trainable
Trainable: an interface common to Layers and GraphVertices that have trainable parameters
|
| Class and Description |
|---|
| MaskState
MaskState: specifies whether a mask should be applied or not.
|
| OptimizationAlgorithm
Optimization algorithm to use
|
| Class and Description |
|---|
| Layer
Interface for a layer of a neural network.
|
| Class and Description |
|---|
| MaskState
MaskState: specifies whether a mask should be applied or not.
|
| TrainingConfig
Simple interface for the training configuration (updater, L1/L2 values, etc) for trainable layers/vertices.
|
| Class and Description |
|---|
| Layer
Interface for a layer of a neural network.
|
| MaskState
MaskState: specifies whether a mask should be applied or not.
|
| ParamInitializer
Param initializer for a layer
|
| TrainingConfig
Simple interface for the training configuration (updater, L1/L2 values, etc) for trainable layers/vertices.
|
| Class and Description |
|---|
| Layer
Interface for a layer of a neural network.
|
| TrainingConfig
Simple interface for the training configuration (updater, L1/L2 values, etc) for trainable layers/vertices.
|
| Class and Description |
|---|
| ParamInitializer
Param initializer for a layer
|
| TrainingConfig
Simple interface for the training configuration (updater, L1/L2 values, etc) for trainable layers/vertices.
|
| Class and Description |
|---|
| ParamInitializer
Param initializer for a layer
|
| TrainingConfig
Simple interface for the training configuration (updater, L1/L2 values, etc) for trainable layers/vertices.
|
| Class and Description |
|---|
| Layer
Interface for a layer of a neural network.
|
| ParamInitializer
Param initializer for a layer
|
| TrainingConfig
Simple interface for the training configuration (updater, L1/L2 values, etc) for trainable layers/vertices.
|
| Class and Description |
|---|
| Layer
Interface for a layer of a neural network.
|
| MaskState
MaskState: specifies whether a mask should be applied or not.
|
| ParamInitializer
Param initializer for a layer
|
| TrainingConfig
Simple interface for the training configuration (updater, L1/L2 values, etc) for trainable layers/vertices.
|
| Class and Description |
|---|
| ParamInitializer
Param initializer for a layer
|
| TrainingConfig
Simple interface for the training configuration (updater, L1/L2 values, etc) for trainable layers/vertices.
|
| Class and Description |
|---|
| ParamInitializer
Param initializer for a layer
|
| TrainingConfig
Simple interface for the training configuration (updater, L1/L2 values, etc) for trainable layers/vertices.
|
| Class and Description |
|---|
| ParamInitializer
Param initializer for a layer
|
| TrainingConfig
Simple interface for the training configuration (updater, L1/L2 values, etc) for trainable layers/vertices.
|
| Class and Description |
|---|
| TrainingConfig
Simple interface for the training configuration (updater, L1/L2 values, etc) for trainable layers/vertices.
|
| Class and Description |
|---|
| ParamInitializer
Param initializer for a layer
|
| TrainingConfig
Simple interface for the training configuration (updater, L1/L2 values, etc) for trainable layers/vertices.
|
| Class and Description |
|---|
| MaskState
MaskState: specifies whether a mask should be applied or not.
|
| Class and Description |
|---|
| Layer
Interface for a layer of a neural network.
|
| Class and Description |
|---|
| FwdPassType
Type of forward pass to do.
|
| Layer
Interface for a layer of a neural network.
|
| Model
A Model is meant for predicting something from data.
|
| NeuralNetwork |
| Class and Description |
|---|
| Layer
Interface for a layer of a neural network.
|
| MaskState
MaskState: specifies whether a mask should be applied or not.
|
| Trainable
Trainable: an interface common to Layers and GraphVertices that have trainable parameters
|
| TrainingConfig
Simple interface for the training configuration (updater, L1/L2 values, etc) for trainable layers/vertices.
|
| Class and Description |
|---|
| Layer
Interface for a layer of a neural network.
|
| MaskState
MaskState: specifies whether a mask should be applied or not.
|
| Trainable
Trainable: an interface common to Layers and GraphVertices that have trainable parameters
|
| TrainingConfig
Simple interface for the training configuration (updater, L1/L2 values, etc) for trainable layers/vertices.
|
| Class and Description |
|---|
| Layer
Interface for a layer of a neural network.
|
| MaskState
MaskState: specifies whether a mask should be applied or not.
|
| Trainable
Trainable: an interface common to Layers and GraphVertices that have trainable parameters
|
| Class and Description |
|---|
| Classifier
A classifier (this is for supervised learning)
|
| Layer
Interface for a layer of a neural network.
|
| Layer.TrainingMode |
| Layer.Type |
| MaskState
MaskState: specifies whether a mask should be applied or not.
|
| Model
A Model is meant for predicting something from data.
|
| Trainable
Trainable: an interface common to Layers and GraphVertices that have trainable parameters
|
| TrainingConfig
Simple interface for the training configuration (updater, L1/L2 values, etc) for trainable layers/vertices.
|
| Class and Description |
|---|
| Classifier
A classifier (this is for supervised learning)
|
| Layer
Interface for a layer of a neural network.
|
| Layer.Type |
| MaskState
MaskState: specifies whether a mask should be applied or not.
|
| Model
A Model is meant for predicting something from data.
|
| Trainable
Trainable: an interface common to Layers and GraphVertices that have trainable parameters
|
| Class and Description |
|---|
| Layer
Interface for a layer of a neural network.
|
| Layer.Type |
| MaskState
MaskState: specifies whether a mask should be applied or not.
|
| Model
A Model is meant for predicting something from data.
|
| Trainable
Trainable: an interface common to Layers and GraphVertices that have trainable parameters
|
| Class and Description |
|---|
| Layer
Interface for a layer of a neural network.
|
| Layer.Type |
| Model
A Model is meant for predicting something from data.
|
| Trainable
Trainable: an interface common to Layers and GraphVertices that have trainable parameters
|
| Class and Description |
|---|
| Layer
Interface for a layer of a neural network.
|
| Layer.Type |
| Model
A Model is meant for predicting something from data.
|
| Trainable
Trainable: an interface common to Layers and GraphVertices that have trainable parameters
|
| Class and Description |
|---|
| Layer
Interface for a layer of a neural network.
|
| Model
A Model is meant for predicting something from data.
|
| Trainable
Trainable: an interface common to Layers and GraphVertices that have trainable parameters
|
| Class and Description |
|---|
| Layer
Interface for a layer of a neural network.
|
| Model
A Model is meant for predicting something from data.
|
| Trainable
Trainable: an interface common to Layers and GraphVertices that have trainable parameters
|
| Class and Description |
|---|
| Layer
Interface for a layer of a neural network.
|
| Model
A Model is meant for predicting something from data.
|
| Trainable
Trainable: an interface common to Layers and GraphVertices that have trainable parameters
|
| Class and Description |
|---|
| Layer
Interface for a layer of a neural network.
|
| Layer.Type |
| Model
A Model is meant for predicting something from data.
|
| Trainable
Trainable: an interface common to Layers and GraphVertices that have trainable parameters
|
| Class and Description |
|---|
| Layer
Interface for a layer of a neural network.
|
| Class and Description |
|---|
| Layer
Interface for a layer of a neural network.
|
| Layer.TrainingMode |
| Layer.Type |
| Model
A Model is meant for predicting something from data.
|
| Trainable
Trainable: an interface common to Layers and GraphVertices that have trainable parameters
|
| Class and Description |
|---|
| Classifier
A classifier (this is for supervised learning)
|
| Layer
Interface for a layer of a neural network.
|
| Model
A Model is meant for predicting something from data.
|
| Trainable
Trainable: an interface common to Layers and GraphVertices that have trainable parameters
|
| Class and Description |
|---|
| Classifier
A classifier (this is for supervised learning)
|
| Layer
Interface for a layer of a neural network.
|
| Layer.Type |
| Model
A Model is meant for predicting something from data.
|
| ParamInitializer
Param initializer for a layer
|
| Trainable
Trainable: an interface common to Layers and GraphVertices that have trainable parameters
|
| Class and Description |
|---|
| Layer
Interface for a layer of a neural network.
|
| Layer.Type |
| MaskState
MaskState: specifies whether a mask should be applied or not.
|
| Model
A Model is meant for predicting something from data.
|
| Trainable
Trainable: an interface common to Layers and GraphVertices that have trainable parameters
|
| Class and Description |
|---|
| Classifier
A classifier (this is for supervised learning)
|
| Layer
Interface for a layer of a neural network.
|
| Layer.Type |
| MaskState
MaskState: specifies whether a mask should be applied or not.
|
| Model
A Model is meant for predicting something from data.
|
| Trainable
Trainable: an interface common to Layers and GraphVertices that have trainable parameters
|
| TrainingConfig
Simple interface for the training configuration (updater, L1/L2 values, etc) for trainable layers/vertices.
|
| Class and Description |
|---|
| Classifier
A classifier (this is for supervised learning)
|
| Layer
Interface for a layer of a neural network.
|
| MaskState
MaskState: specifies whether a mask should be applied or not.
|
| Model
A Model is meant for predicting something from data.
|
| Trainable
Trainable: an interface common to Layers and GraphVertices that have trainable parameters
|
| TrainingConfig
Simple interface for the training configuration (updater, L1/L2 values, etc) for trainable layers/vertices.
|
| Class and Description |
|---|
| Classifier
A classifier (this is for supervised learning)
|
| Layer
Interface for a layer of a neural network.
|
| Model
A Model is meant for predicting something from data.
|
| Trainable
Trainable: an interface common to Layers and GraphVertices that have trainable parameters
|
| Class and Description |
|---|
| Layer
Interface for a layer of a neural network.
|
| Model
A Model is meant for predicting something from data.
|
| Trainable
Trainable: an interface common to Layers and GraphVertices that have trainable parameters
|
| TrainingConfig
Simple interface for the training configuration (updater, L1/L2 values, etc) for trainable layers/vertices.
|
| Class and Description |
|---|
| Layer
Interface for a layer of a neural network.
|
| Layer.Type |
| MaskState
MaskState: specifies whether a mask should be applied or not.
|
| Model
A Model is meant for predicting something from data.
|
| Trainable
Trainable: an interface common to Layers and GraphVertices that have trainable parameters
|
| Class and Description |
|---|
| Layer
Interface for a layer of a neural network.
|
| Layer.Type |
| MaskState
MaskState: specifies whether a mask should be applied or not.
|
| Model
A Model is meant for predicting something from data.
|
| Trainable
Trainable: an interface common to Layers and GraphVertices that have trainable parameters
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Layer
Interface for a layer of a neural network.
|
| Class and Description |
|---|
| Layer
Interface for a layer of a neural network.
|
| Model
A Model is meant for predicting something from data.
|
| ParamInitializer
Param initializer for a layer
|
| Trainable
Trainable: an interface common to Layers and GraphVertices that have trainable parameters
|
| TrainingConfig
Simple interface for the training configuration (updater, L1/L2 values, etc) for trainable layers/vertices.
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Classifier
A classifier (this is for supervised learning)
|
| FwdPassType
Type of forward pass to do.
|
| Layer
Interface for a layer of a neural network.
|
| Layer.TrainingMode |
| Layer.Type |
| MaskState
MaskState: specifies whether a mask should be applied or not.
|
| Model
A Model is meant for predicting something from data.
|
| NeuralNetwork |
| Trainable
Trainable: an interface common to Layers and GraphVertices that have trainable parameters
|
| TrainingConfig
Simple interface for the training configuration (updater, L1/L2 values, etc) for trainable layers/vertices.
|
| Updater
Update the model
|
| Class and Description |
|---|
| ParamInitializer
Param initializer for a layer
|
| Class and Description |
|---|
| OptimizationAlgorithm
Optimization algorithm to use
|
| Class and Description |
|---|
| Layer
Interface for a layer of a neural network.
|
| Model
A Model is meant for predicting something from data.
|
| Trainable
Trainable: an interface common to Layers and GraphVertices that have trainable parameters
|
| Updater
Update the model
|
| Class and Description |
|---|
| Trainable
Trainable: an interface common to Layers and GraphVertices that have trainable parameters
|
| Updater
Update the model
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Updater
Update the model
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Updater
Update the model
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| ModelAdapter
This interface describes abstraction that uses provided model to convert INDArrays to some specific output
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| ModelAdapter
This interface describes abstraction that uses provided model to convert INDArrays to some specific output
|
| Class and Description |
|---|
| NeuralNetwork |
| Class and Description |
|---|
| NeuralNetwork |
| Class and Description |
|---|
| NeuralNetwork |
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
| Class and Description |
|---|
| Model
A Model is meant for predicting something from data.
|
Copyright © 2020. All rights reserved.