SLGeometry - an extendable Silverlight geometry framework
September 26, 2012.
SLGeometry (formerly Geometrijica) is a free, extensible, interactive geometry environment that is
being developed at the Department of Mathematics and Computer
Science, Faculty of Science, University of Novi Sad. There are many
dynamic geometry systems already available, some of them free, some
not. The idea behind SLGeometry is to explore some development ideas that we've had, and could not
wait for others to implement.
As of September 2012, Geometrijica is still far from completed, but coming closer to what might be called an 'alpha' version. Most
obviously lacking is the GUI, which is not particularly interactive
at the moment. However, the expression evaluator is fully
functional, so you can give it textual commands (look at the
Examples section below).
We've improved the importing of external functions and UI controls. Now you can import the contents of a DLL file into SLGeometry, and the functions and UI controls will appear in it automatically.
The Visual Studio solution (provided below) runs on Silverlight 5.
The source code can also be compiled and run on WPF.
SLGeometry stands for "Silverlight Geometry", and probably "Sweet Little Geometry" too.
Authors
Đorđe Herceg and
Davorka Radaković,
Department of Mathematics and Informatics, Faculty of Sciences,
University of Novi Sad
Download
You can obtain the current version of SLGeometry (Visual Studio
2012 solution) here:
You are free to use and modify SLGeometry, as long as it is used for
educational and non-commercial purposes and you credit us, the original
developers.
Online SLGeometry
You can try the current version of SLGeometry in your browser. It is under
development, so it still has some bugs and some things do not work as expected.
Examples (outdated)
This section is outdated.
First click the link below to open SLGeometry. Then copy the
commands from either Example 1 or Example 2 into the input box at
the bottom of the window and press Enter.
Example 1 |
Example 2 |
a = (2, -3)
b = (7, -5.2)
c = Segment(a, b)
d = c.Midpoint
e = (5, -5)
f = Segment(c.Midpoint, e)
b.X = 9.3
|
a = (0, 0) b = Clock(a, 10, 13, 00) c = (0, -3) d = Slider(c, 0) b.Hour = d.Value e = CheckBox((3, 0), true) f = If(e.Checked, Clock((3, -1), d.Value, 13, 0))
|
In Example 2, as you move the slider, the hour hand on the clock
moves. You can show/hide the second clock by clicking the checkbox.
The clock is an example of how arbitrary Silverlight controls can be added
to Geometrijica.
We have the tutorial How to add the clock
control to SLGeometry on a separate page.

How does it work?
Behind the scenes, everything you see is represented by
expressions. Numbers, strings, logical values, mathematical
functions, points, segments, lines, buttons and text boxes all have
their respective expression forms. For example, when you write 2+3
your input gets transformed into Plus(2,3).
Expressions are assigned to variables, and if the value of a
variable is something visual, like a point or a text box, it appears on the screen.
When a variable is referenced in an expression, that expression
becomes dependent. For example, the point a=(2,3) is an
independent expression, while b=(a.X+1,
5) is a point which depends on a. Circular references are not allowed. When a
value of a variable changes, all dependent variables are
recalculated automatically. As a consequence, visual objects on the
screen are also updated and the drawing "moves".
Note that, in the previous example, we used a.X
to access the x-coordinate of the point a. You can use the well-known dot notation to access
properties of objects. Some objects have properties that are visual
objects themselves. In order to show such a visual property on screen,
you need to assign it to a variable. Let's have a look at the
following example:
a = (2, -3)
b = (7, -5.2)
c = Segment(a, b)
d = c.Midpoint
e = (5, -5)
f = Segment(c.Midpoint, e)
b.X = 9.3
First we defined the points a and b. Then we created a
segment between them. By assigning the segment's midpoint to the
variable d, it becomes visible on screen. Now we define a new point e
and
we create another segment between the midpoint of the segment c and
point e. Finally, when we move point b along the x-axis, the
whole drawing is recalculated and updated.
The structure of SLGeometry
The system consists of three main parts:
- Parser with expression factory, used to transform user input
into expressions
- Expression evaluation engine, which stores expressions in
named variables and evaluates them
- GeoCanvas, used to draw shapes on screen
The parser was built with COCO/R from an attributed grammar. It
is still under development and some features are missing. Geometrijica accepts text input from the user, parses it and
builds expressions which are then stored in named variables in the
evaluation engine. Various expression types exist to represent
numbers, strings, geometrical objects, mathematical functions and
other objects, such as function graphs and GUI controls. Most
expression types have a visual representation that can be drawn on
screen. This is either a Silverlight control or a collection of
graphics primitives. The GeoCanvas is a visual control on which
geometrical shapes and other objects are displayed. It works in the
Cartesian coordinate system, and supports panning and zooming.
In order to use Geometrijica in your own project, you need to
reference the Geometrijica DLL, put the GeoCanvas control
on a form, and register it with the evaluation engine by typing
geoCanvas1.BindToEngine(Engine.Default);
Currently only the default instance of the evaluation engine can
be used, but multiple GeoCanvas controls can be registered with it.
Expressions
The GExpression class server a sa basis from which all expressions derive. It
contains methods that are common for all expressions:
- public abstract GExpression Eval() - called by the evaluation engine to
obtain the result of the expression;
- public virtual GExpression[] GetChildren() - returns an array of
expressions that this expression contains;
- public virtual List<string> DependsOn(bool recursive) - returns a list
of variable names that this expression depends on;
- public virtual Type VisualType - returns the type of the corresponding
visual class, or null if the expression does not have a visual
representation;
- public virtual GExpression GetPropertyValue(string name) - returns the
value of a named property, if the property exists.
Derived classes can override these methods in order to provide their own
implementation.
The Number class is the simplest implementation of an expression. It contains
one public property, Value, of the type double, and a simple Eval method:
public override GExpression Eval()
{
return this;
}
In other words, the result of evaluating of a number returns the
number itself. Classes Logical and SString are
implemented in a similar way.
Simple functions, such as square root (Sqrt) and logical negation (Not), have arguments,
which we call child expressions. When a function is
evaluated, child expressions are evaluated first. The results are
then used to calculate the result of the function. There is no need
to handle exceptions in the Eval method, as they are handled by the
evaluation engine. Here is the Eval method from the Not expression class:
private GExpression _x;
Logical _result = new Logical(false);
public override GExpression Eval()
{
GExpression x = _x.Eval();
_result.Value = !((Logical)x).Value;
return _result;
}
Optimizing for speed
In the previous example, the resulting expression object is
created only once and kept in the variable _result. Upon each new evaluation this object is updated
and returned to the evaluation engine. This way we avoid the
unneccessary creation of new objects for each evaluation cycle. This
speeds up evaluation and helps reduce the time spent on garbage
collection.
The Fn class
Although functions can be implemented by deriving from the GExpression
class directly, in most cases, this is not the best
way. We provide the Fn class, which implements behavior
common for all functions:
- Management of signatures, which are used in the parser. When
a function name is recognized by the parser, arguments are
matched with the registered signatures. Only after a match is
found, is the function object instantiated and populated with
the arguments.
- Support for argument lists, containing either unnamed or
named arguments;
- Evaluation of arguments;
Below is an example of the Plus function. The function operates on lists
of two or more arguments, as specified by the UnnamedArgsSignature. Note the
second parameter of the [Function("Plus", true)]
attribute. It says that this function's expression tree can be flattened. In
other words, after parsing an expression like a+b+c,
the resulting expression tree will be Plus(a, Plus(b,
c)). Functions marked as flat will go through the Flatten
method, which will put all arguments under one function, and the expression
tree will be transformed to Plus(a, b, c).
This obviously speeds up subsequent evaluations of the expression.
In the Eval method we cast all arguments to numbers, add them together
and return the result. Note that the function performs no type checking.
Should a cast error occur, an exception will be raised. This is perfectly
legal, as the exception will be handled by the Engine class.
using System;
using Geometrijica.Expressions;
using System.Text;
using Geometrijica.Functions;
namespace Geometrijica.Basic
{
[Function("Plus", true)]
public class Plus : Fn
{
static Plus()
{
Fn.RegisterSignatures(typeof(Plus),
new SignatureCollection(
new UnnamedArgsSignature(2, int.MaxValue)
)
);
}
private Number _result = new Number(0);
public override Expressions.GExpression Eval()
{
EvaluateProperties();
double sum = 0.0;
foreach (FnArg fa in UnnamedArgs)
{
sum += (((Number)(fa.Expression.Eval())).Value);
}
_result.Value = sum;
return _result;
}
public override string ToString()
{
return PrefixForm("Plus", UnnamedArgs.Expressions);
}
}
}
There is a lot more to be explained. We will post more updates soon. If
you have any questions or comments, please contact us.