This extension is a port of the Eukleides geometrical drawing language into Numbas’ JME system. In order to be compatible with JME, this version occasionally works differently to the original Eukleides language. The basic flow of constructing points and then drawing them has been retained, but some of the syntax is different.
The function eukleides
creates a diagram which you can include anywhere in a Numbas question.
A diagram consists of a list of objects, drawn into a frame which is shown on screen. The boundaries of the frame are automatically determined, or you can give explicit values.
The basic objects in a diagram are:
Drawing modifiers change how an object is drawn, for example changing the colour or size. Modifiers can be applied to single objects, lists of objects, or even chained together to create reusable styles.
All coordinates in Eukleides diagrams are specified in terms of the frame and then transformed onto the browser’s coordinates.
The bottom-left of the diagram has coordinates (min_x,min_y)
and the top-right of the diagram has coordinates (max_x,max_y)
.
The positive horizontal direction is right, and the positive vertical direction is up.
Angles are measured anti-clockwise.
The bearing 0 points right.
Angles can be given in radians, e.g. rad(pi/4)
, or degrees, e.g. degrees(45)
.
It’s important that diagrams are accessible to as many people as possible. The Eukleides extension provides a variety of ways of doing this. It tries to do as much as it can automatically to improve accessibility, but you should bear accessibility in mind when designing diagrams.
Users who can’t see the diagram might access it through a screenreader.
The first argument to the eukleides
function is a title for the diagram, which a screenreader will read out.
This title should briefly identify the diagram and describe its contents, for example, “A circle inscribed in a square”, or “Bar chart showing number of customers against month”.
The Web Accessibility Initiative has a good tutorial on describing complex images.
The user can then navigate inside the diagram to have individual objects described.
Eukleides automatically adds a text description to each object in the diagram. At minimum, this is just the name of the object, for example ‘line’ or ‘circle’. If the object is defined in terms of points which have been labelled, those will be used: “triangle through A, B, C” for example.
The object’s colour and drawing style will also be described, for example “gray dotted line segment through A, B”.
The text descriptions are designed not to reveal any information which is not visually obvious.
If you add the verbose
modifier to an object, information such as coordinates and angles will be used to provide a more precise description of the object.
While these descriptions can give a rough idea of the contents of a diagram, information such as relative positions or implicit meanings of objects will be missing.
You can manually set the description for an object with the description
function.
For example, a circle inscribed in a square will just be described as “circle”; “circle inscribed in square A B C D” would be a more helpful description.
Eukleides provides a variety of colour schemes which have been designed to maximise accessibility, considering colour vision deficiency and other vision impairments. Most of these colour schemes are drawn from ColorBrewer.
The variables color1
to color6
are assigned to a set of colours which should be easily distinguishable by almost all sighted people, including those with colour vision deficiency.
The functions qualitative_color_scheme
, sequential_color_scheme
and divergent_color_scheme
return lists of colours designed for different uses:
You can select other colours with the color()
modifier, which accepts any colour specification accepted by CSS, such as hexadecimal format, RGB or HSL.
You should not use other colours without considering contrast and CVD.
The WebAIM link constrast checker is a good first port of call to check whether a pair of colours can be easily distinguished against each other and the background.
It’s a good idea to use aspects other than colour, such as the dotted
and dashed
modifiers, to distinguish objects.
Eukleides diagrams can be made dynamic in several ways.
The variable time
represents the number of seconds since the diagram was created.
Diagrams which contain references to time
are continually redrawn with updated values for time
.
The variables mousex
and mousey
represent the position of the mouse cursor, or the position of the last touch on touchscreen devices, in diagram coordinates.
When the mouse moves, the diagram is redrawn with update values of these variables.
A point on the diagram can be made draggable by applying the modifier draggable()
to it when drawing it.
Any other free variables in the diagram are interpreted as free parameters, and given the initial value of 0
.
You can explicitly give initial values for free variables in a dictionary as the last argument to the eukleides
function.
When the user moves a draggable point, the system tries to position it as close as possible to the mouse by changing the values of the free variables. It does this by repeatedly picking values for the variables, redrawing the diagram, and measuring the distance between the drawn point and the cursor. A gradient descent algorithm homes in on the best solution within a few iterations.
The great advantage of this system is that you don’t need to explicitly provide a mapping between target coordinates and the free variables in your diagram. The algorithm will find a solution, even if the position of the dragged point is calculated indirectly.
Note: The solver algorithm assumes that draggable points move continuously, so will fail if the point’s position can only take discrete values. A workaround is to have a draggable point which can move continuously, and a second non-interactive point which takes the position you really want.
By default, moving a draggable point can affect any of the free variables in a diagram.
You can give the draggable
function an optional list of variable names that it’s allowed to affect.
The basic template for a Eukleides diagram is the following:
eukleides("A diagram",[
point(1,0)
, point(1,1) .. point(4,0)
])
The first argument of the eukleides
function is a title, and the second argument is a list of objects to draw.
The great power of Eukleides lies in its ability to construct geometrical objects from other objects.
In order to achieve this, use the let
function to temporarily assign names to objects so you can refer back to them:
eukleides("A triangle",
let(
a, point(1,0)
, b, point(3,1)
, c, point(2,3)
, [
a..b..c filled color1
, a..b..c
]
)
)
The example above constructs three points, draws a filled triangle with those vertices, and then draws its outline.
There are several functions which construct common polygons, given a mixture of points and side lengths or angles.
eukleides("A right-angled triangle",
let(
[a,b,c], right(2,3)
, [
a..b..c
, a,b,c
]
)
)
The example above constructs a right-angled triangle with side lengths 2 and 3. The function right
returns a set of points, which are assigned the names a
, b
and c
.
We then draw the outline of the triangle, and dots at each of the points.
Note that constructing an object doesn’t mean it’s automatically drawn.
The second argument of eukleides
evaluates to a list of objects to be drawn - you can draw objects more than once, in different styles, or not at all.
You can use vectors to translate any object in a diagram.
eukleides("A parallelogram",
let(
u, vector(1,0)
, v, vector(1,2)
, a, point(0,0)
, [
a, a+u, a+v, (a+u..a..a+v) open
]
)
)
The example above translates the point a
by two different vectors.
When you repeat a construction, you can often make your code easier to read by using the map
function:
eukleides("Three congruent parallelograms",
let(
u, vector(1,0)
, v, vector(1,2)
, a, point(0,0)
, b, point(4,2)
, c, point(-1,3)
, map(
[x, x+u, x+v, (x+u..x..x+v) open]
, x
, [a,b,c]
)
)
)
Drawing modifiers change aspects of how objects are drawn, such as colour or shading style.
When drawing an object, just write the modifier immediately afterwards to apply it.
A modified object becomes a drawing
data type, which collects up the object(s) being drawn with the modifiers, so you can’t use the result in further constructions.
To minimise the inconvenience, we try to apply modifiers last: for example, (origin red) .. point(2,0)
doesn’t work, but origin .. point(2,0) red
does - the line segment is constructed before the red
modifier is applied.
You can specify the bounding box of the diagram by providing the coordinates of the bottom-left and top-right of the box after the description:
eukleides("Axes and a point at (8,3)", -1,-1, 10,5,
[ line(origin,deg(0))
, line(origin,deg(90))
, point(8,3)
]
)
If you don’t specify the bounding box, then Eukleides automatically picks one, containing all of the drawn elements.
To include a Eukleides diagram in a Numbas question, first enable the Eukleides extension.
Then, create a variable whose definition is the code for your diagram, i.e. eukleides(title,objects)
.
In the following, suppose the variable is called diagram
.
To show the diagram to the student, type {diagram}
in a content area, such as the question statement, a part prompt, or the advice.
The diagram will be inserted into the content at this point.
The definition of your diagram can refer to other question variables, but any values set interactively by the diagram (all free variable names in the diagram’s definition) can’t be used by other question variables - the values of question variables are only evaluated once, when the question is created.
Colours: black, color1, color2, color3, color4, color5, color6, darkgray, gray, lightgray, white
forth
or back
.half
is applied, draw lines and curves in the backward direction. Arrows on line segments are drawn on the first point. Arrows on angle labels are drawn pointing clockwise.dotted
is also applied, angle labels will instead be labelled with two dots.half
is applied, draw lines and curves in the forward direction. Arrows on line segments are drawn on the last point. Arrows on angle labels are drawn pointing anti-clockwise.dotted
is also applied, angle labels will instead be labelled with three dots.(a
: number
) *
(b
: angle
) → angle
Multiply an angle by the given scale factor.
(a
: angle
) *
(b
: number
) → angle
Multiply an angle by the given scale factor.
(d1
: drawing
) *
(d2
: drawing
) → drawing
Combine two drawings.
(set
: point set
) *
(drawing
: drawing
) → drawing
Add a drawing modifier to a set of points.
(list
: list
) *
(drawing
: drawing
) → drawing
Add a drawing modifier to a list of objects.
(anything) *
(drawing
) → drawing
Add a drawing modifier to an object.
(point
| line
| point set
| circle
| conic
| drawing
| angle label
| list
) -
(vector
) → ?
Translate an object or list of objects by the opposite of the given vector.
(a
: angle
) -
(b
: angle
) → angle
Subtract two angles.
(a
: point
) -
(b
: point
) → vector
Vector from the second point's position to the first's.
(a
: point
) ..
(b
: point
) → point set
A segment between two points.
(set
: point set
) ..
(p
: point
) → point set
Add a point to the end of a polygon.
(p
: point
) ..
(set
: point set
) → point set
Add a point to the start of a polygon.
(a
: point set
) ..
(b
: point set
) → point set
Concatenate two polygons.
(p1
: point
) ..
(p2
: drawing of point
) → drawing
A segment between two points.
(set
: point set
) ..
(p
: drawing of point
) → drawing
Add a point to the end of a polygon.
(p
: point
) ..
(set
: drawing of point set
) → drawing
Add a point to the start of a polygon.
angle
(a
: point
, b
: point
, c
: point
) → angle label
Draw an angle label.
equilateral
(p1
: point
, p2
: point
) → list
equilateral
([p1
: point
], l1
: number
, [orientation
: angle
]) → list
Create an equilateral triangle from the given points. Can give up two two vertices; if fewer than two vertices are given must give the length of the first side and optionally the orientation of the first side.
homothetic
(p
: point
, origin
: point
, k
: number
) → point
Homothecy (reduction or dilation) of the first point with respect to the second, and the given scale.
homothetic
(line
: line
, origin
: point
, k
: number
) → line
Homothecy (reduction or dilation) of a line with respect to the given point and scale factor.
homothetic
(conic
: conic
, origin
: point
, k
: number
) → conic
Homothecy (reduction or dilation) of a conic with respect to the given point and scaling factor.
intersection
(l1
: line
, l2
: line
) → point
The intersection point of two lines.
intersection
(l
: line
, set
: point set
) → point set
All points at which the line intersects the perimeter of the given point set.
intersection
(l
: line
, c
: circle
) → point set
All points at which the line intersects the given circle.
intersection
(l
: line
, c
: conic
) → point set
All points at which the tline intersects the given conic.
intersection
(s1
: point set
, s2
: point set
) → point set
All points at which the perimeters of the two point sets intersect.
intersection
(c1
: circle
, c2
: circle
) → point set
The points of intersection of the two circles.
intersection
(s
: point set
, c
: circle
) → point set
All points of intersection of the perimeter of the point set with the given circle.
isosceles
(p1
: point
, p1
: point
, l2
: number
| a1
: angle
) → list
isosceles
([p1
: point
], [p2
: point
], [l1
: number
, l2
: number
| a1
: angle
, [orientation
: angle
]]) → list
Create an isosceles triangle from the given points. Can give up to two vertices; one other length or angle; and the orientation of the first side if fewer than two vertices given.
parallelogram
(p1
: point
, p2
: point
, p3
: point
) → list
parallelogram
(p1
: point
, p2
: point
, [l2
: number
, a1
: angle
]) → list
parallelogram
([p1
: point
], [l1
: number
, l2
: number
, a1
: angle
, [orientation
: angle
]]) → list
Create a parallelogram from the given points. Can give up to three vertices. If fewer than two vertices given, must give the length of the first side, one more side and an angle, and optionally the orientation of the first side.
point
(x
: number
, y
: number
) → point
A point at the given coordinates.
point
(r
: number
, a
: angle
) → point
A point at the given polar coordinates.
point
(set
: point set
, t
: number
) → point
A point along the first edge of the given polygon.
point
(line
: line
, t
: number
) → point
A point on the given line, the given distance away from its origin.
point
(circle
: circle
, a
: angle
) → point
A point on the given circle at the given angle.
point
(conic
: conic
, t
: number
) → point
A point with the given argument on the given conic.
rectangle
(p1
: point
, p2
: point
, [l2
: number
]) → list
rectangle
([p1
: point
], [l1
: number
, l2
: number
, [orientation
: angle
]]) → list
Create a rectangle from the given points. Can give up to two vertices. If fewer than two vertices given, must give the length of the first side. Must give the length of one more side and optionally the orientation of the first side.
right
(p1
: point
, p2
: point
, [l2
: number
| a1
: angle
], [orientation
: angle
]) → list
right
([p1
: point
], [p2
: point
], [l1
: number
, [l2
: number
| a1
: angle
], [orientation
: angle
]]) → list
Create a right-angled triangle from the given parameters. Can give up to two vertices; one other length or angle; and the orientation of the first side if fewer than two vertices given.
rotate
(p
: point
, origin
: point
, angle
: angle
) → point
Rotate the first point the given angle around the second.
rotate
(v
: vector
, a
: angle
) → vector
Rotate a vector by the given angle.
rotate
(line
: line
, origin
: point
, angle
: angle
) → line
Rotate a line by the given angle around the given point.
rotate
(set
: point set
, origin
: point
, a
: angle
) → point set
Rotation of a polygon by the given angle around the given point.
rotate
(conic
: conic
, origin
: point
, a
: angle
) → conic
Rotate a conic by the given angle around the given point.
symmetric
(p
: point
, origin
: point
) → point
180° rotation of the first point around the second.
symmetric
(line
: line
, p
: point
) → line
180° degree rotation of a line around the given point.
symmetric
(set
: point set
, p
: point
) → point set
180° degree rotation of the given polygon around the given point.
symmetric
(conic
: conic
, p
: point
) → conic
180° rotation of the given conic around the given point.
triangle
(p1
: point
, p2
: point
, [l2
: number
| a2
: angle
]) → list
triangle
(p1
: point
, [l1
: number
, [[l2
: number
| a2
: angle
]]], [orientation
: angle
]) → list
triangle
([l1
: number
, [[l2
: number
| a2
: angle
], [l3
: number
| a3
: angle
]]], [orientation
: angle
]) → list
Create a triangle from the given parameters. Can give up to two vertices; any remaining lengths or angles; and the orientation of the first side if fewer than two vertices given.