Skip to content

Writing interactive exercises

Povilas edited this page Jan 7, 2014 · 5 revisions

The interactive utility module provides some helpful tools for creating interactive exercises. All of the interactions typically take place inside a single graphie element, so to start, you'd create a graphie element to contain your interaction:

    <div class="problem">
      <div class="graphie">

        init({
          range: [ [ -5, 5 ], [ -5, 5 ] ]
        });

        addMouseLayer();

      </div>
    </div>

The init() function initializes graphie and the addMouseLayer() function adds an invisible layer on top of the graph that is used by the interactive utilities to capture mouse events. addMouseLayer() also does some other setup, so basically if you're using any of the interactive utilities, you want to call this right after the graphie init(). Note that it will also disable the scratchpad. Normally this won't be a problem, since the user will be using the mouse to interact with your nifty exercise, but it's something to be aware of.

Interactive utilities

addMouseLayer()

This basically initializes the interactive functions. Call it after the graphie init() call but before you call any interactive functions.


addMovablePoint( options )

This adds a point to the graph that can be dragged around. It allows automatic constraints on its movement as well as automatically managing line segments that terminate at the point.

Options can be set to control how the point behaves:

  • coord[]: The initial position of the point

  • snapX, snapY: The minimum increment the point can be moved

  • visible: Set to false for the point to be invisible. This might be useful since you can still attach line segments to the point and still move the point programmatically. You might also want this to use in defining a fixedDistance or fixedAngle constraint for another point.

  • constraints: An object describing constraints on the point's movement:

    • constraints.fixed: Set to true to make the point immovable. You can still move it programmatically of course.

    • constraints.constrainX: Set to true to only permit the point to be dragged up and down.

    • constraints.constrainY: Set to true to only permit the point to be dragged left and right.

    • constraints.fixedDistance: This object allows you to constrain the point to always be a fixed distance from another point. In other words, it gives you a point that moves in a circle. Set fixedDistance.dist to the radius of the circle and fixedDistance.point to a (possibly invisible) movablePoint or simply a coordinate pair [x, y] that defines the center.

    • constraints.fixedAngle: This object allows you to constrain the point to only move in a straight line at a particular angle. You provide the angle and the coordinates of the vertex and another ref point point that together define the line along which the point may move.

  • normalStyle, highlightStyle: Raphael-type style attributes that allow you to override the appearance of the point.

The return value is a movablePoint object that can be used to manipulate the point:

  • The coord[] property tells you the current (x, y) position.

  • Additionally any of the other options and constraints above can be set after the point is created to change its behavior. For example, you can add or remove a constraint.

  • By adding an onMove( x, y ) method to the returned object, you can install an event handler that gets called every time the user moves the point. The onMove() method is passed the proposed x and y coordinates. If you return false from your onMove() method, you veto the movement. You can use this to provide custom constraints on where the point can move. If that's not enough, you can also return an array [ x, y ] that overrides the point's position.

  • The returned object also provides a moveTo( x, y, updateLineEnds ) method that will move the point to a specific (x, y) coordinate with an animation. If you've attached any line segments to the point (see below), you can pass true for updateLineEnds and the lines will animate along with the point. If you've defined an onMove() callback, it will get called with the new position.

    • If you don't want the animation of moveTo() or want to do things yourself for some reason, there are some more granular methods: setCoord([ x, y ]) moves the point without any checks, animation, or callbacks. If you're using setCoord() and have any line segments that terminate at the point, you might also want to call updateLineEnds() afterwards to automatically redraw all the line segments attached to the point.
  • You can connect a movableLineSegment (see below) to a movablePoint. The point is attached to a specific end of the line segment by adding the segment either to the list of lines that start at the point or the list of lines that end at the point:

    movablePoint.lineStarts.push( movableLineSegment );
      - or -
    movablePoint.lineEnds.push( movableLineSegment );
    

Normally it's easier to let movableLineSegment do this for you though


addMovableLineSegment( options )

MovableLineSegment is a line segment that can be dragged around the screen. By attaching a movablePoint to each (or one) end, the ends can be manipulated individually.

To use with movablePoints, add the movablePoint first, then:

addMovableLineSegment({
    pointA: movablePoint1,
    pointZ: movablePoint2
});

Or just one end:

addMovableLineSegment({
    pointA: movablePoint1,
    coordZ: [ 0, 0 ]
});

Include fixed: true in the options if you don't want the entire line to be draggable (you can still use points to make the endpoints draggable)

Other options supported are:

  • snap: When dragging the line, the minimum increment it can be moved

  • extendLine: Set to true to extend the line to the edges of the drawing area. In other words it draws a "line" rather than a "line segment"

  • normalStyle, highlightStyle: Raphael-type style attributes that allow you to override the appearance of the line.

The returned object includes the following properties/methods:

  • lineSegment.coordA / lineSegment.coordZ: The coordinates of each end of the line segment

  • lineSegment.transform( syncToPoints ): Repositions the line segment. Call after changing coordA and/or coordZ, or pass syncToPoints = true to use the current position of the corresponding movablePoints, if the segment was defined using movablePoints

Examples

Two points connected by a line segment. The points allow you to drag the ends of the line segment around:

    <div class="graphie">
        init({
            range: [ [ -5, 5 ], [ -5, 5 ] ]
        });

        addMouseLayer();

        graph.point1 = addMovablePoint({
            coord: [ -4, 0 ]
        });

        graph.point2 = addMovablePoint({
            coord: [ 4, 0 ]
        });

        graph.line1 = addMovableLineSegment({
            pointA: graph.point1,
            pointZ: graph.point2,
            fixed: true
        });
    </div>

A point that moves in a circle. Note that point1 is the center of the circle and is set to be invisible. point2 is the one that is visible and draggable, so we add a constraint that forces it to always stay 4 units from point1, hence moving in a circle. This also shows a line segment added between the two points to show the radius:

    <div class="graphie">
        init({
            range: [ [ -5, 5 ], [ -5, 5 ] ]
        });

        addMouseLayer();

        graph.point1 = addMovablePoint({
            coord: [ 0, 0 ],
            visible: false
        });

        graph.point2 = addMovablePoint({
            coord: [ 4, 0 ]
        });

        graph.point2.constraints.fixedDistance = {
            dist: 4,
            point: graph.point1
        };

        graph.line1 = addMovableLineSegment({
            pointA: graph.point1,
            pointZ: graph.point2,
            fixed: true
        });

        graph.point3 = addMovablePoint({ 
            coord: [ 8, 8 ],
            snapY: 1,
            snapX: 1,
        });

        graph.point3.constraints.fixedAngle = {
            angle: 0,
            vertex: { coord: [ 0, 0 ]},
            ref: { coord: [ 20, 20 ]}
        }

    </div>

Checking the answer

Checking the student's answer to an interactive exercise is a little more involved. In most cases you'll need to use the custom answer type.