Đorđe Herceg - Personal web site

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.

Geometrijica - Example 2

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.