JSXGraph logo
JSXGraph
JSXGraph share

Share

Soccer offside line
QR code
<iframe 
    src="https://jsxgraph.uni-bayreuth.de/share/iframe/soccer-offside-line" 
    style="border: 1px solid black; overflow: hidden; width: 550px; aspect-ratio: 55 / 65;" 
    name="JSXGraph example: Soccer offside line" 
    allowfullscreen
></iframe>
This code has to
<div id="board-0-wrapper" class="jxgbox-wrapper " style="width: 100%; ">
   <div id="board-0" class="jxgbox" style="aspect-ratio: 4 / 3; width: 30%;" data-ar="4 / 3"></div>
</div>
<div id="board-1-wrapper" class="jxgbox-wrapper " style="width: 100%; ">
   <div id="board-1" class="jxgbox" style="aspect-ratio: 4 / 3; width: 30%;" data-ar="4 / 3"></div>
</div>
<div id="board-2-wrapper" class="jxgbox-wrapper " style="width: 100%; ">
   <div id="board-2" class="jxgbox" style="aspect-ratio: 4 / 3; width: 30%;" data-ar="4 / 3"></div>
</div>

<script type = "text/javascript"> 
    /*
    This example is licensed under a 
    Creative Commons Attribution ShareAlike 4.0 International License.
    https://creativecommons.org/licenses/by-sa/4.0/
    
    Please note you have to mention 
    The Center of Mobile Learning with Digital Technology
    in the credits.
    */
    
    const BOARDID0 = 'board-0';
    const BOARDID1 = 'board-1';
    const BOARDID2 = 'board-2';
    const BOARDID = BOARDID0;

    const board1 = JXG.JSXGraph.initBoard(BOARDID0, { boundingbox: [-10, 10, 10, -10] });
    const board2 = JXG.JSXGraph.initBoard(BOARDID1, { boundingbox: [-10, 10, 10, -10] });
    const board3 = JXG.JSXGraph.initBoard(BOARDID2, { boundingbox: [-10, 10, 10, -10] });
    
    board1.addChild(board2);
    board1.addChild(board3);
    
    // Polygon in camera image
    var p1 = board1.create('polygon', [[-8, -6], [9, -6], [5, 6.5], [-6, 7]], { fillColor: 'green' });
    
    // Polygon in model view
    var p2 = board2.create('polygon', [[-5, -4], [5, -4], [5, 4], [-5, 4]], {
        fillColor: 'green',
        vertices: { visible: false, fixed: true }
    });
    
    // Polygon on TV screen (copy of p1)
    var p3 = board3.create('polygon', [
             [() => p1.vertices[0].X(), () => p1.vertices[0].Y()],
             [() => p1.vertices[1].X(), () => p1.vertices[1].Y()],
             [() => p1.vertices[2].X(), () => p1.vertices[2].Y()],
             [() => p1.vertices[3].X(), () => p1.vertices[3].Y()]
         ], { fillColor: 'green', vertices: { visible: false } });
    
    //
    // Compute a projective transformation which maps the polygon p1 to the polygon p2.
    //    
    
    // Two global variables containing the transformation matrix (in vector and in matrix form)
    var x_global = [],
        x2_global = [],
        mat_global = [[0, 0, 0], [0, 0, 0], [0, 0, 0]],
        mat2_global = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];
    
    // This function computes the transformation matrix
    var updateTransformationMatrix = function() {
        var i, j, k, M = [];
    
        // Initialise a 13x13 matrix to zero.
        for (i = 0; i < 13; i++) {
            M.push([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
        }
    
        // Set up the system of linear equations:
        // 12 equations and 13 unknowns for the matrix 
        // mat_global such that
        // mat_global * p1 - p2 * (i, j, k, l)^T = 0
        for (i = 0; i < 3; i++) {
            for (j = 0; j < 3; j++) {
                for (k = 0; k < 4; k++) {
                    M[i * 4 + k][i * 3 + j] = p1.vertices[k].coords.usrCoords[j];
                }
            }
        }
        for (i = 0; i < 3; i++) {
            for (k = 0; k < 4; k++) {
                M[i * 4 + k][9 + k] = -p2.vertices[k].coords.usrCoords[i];
            }
        }
        // Equation 13: set mat_global[0][0] = 1.
        // Remember that in JSXGraph the coordinates are ordered by (z, x, y)
        M[12][0] = 1;
    
        // Right hand side vector
        var b = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];
    
        // Solve the system
        x_global = JXG.Math.Numerics.Gauss(M, b);
    
        // Convert the solution vector into matrix form
        for (i = 0; i < 3; i++) {
            for (j = 0; j < 3; j++) {
                mat_global[i][j] = x_global[i * 3 + j];
            }
        }
    
        // Invert the matrix to get the inverse transform
        mat2_global = JXG.Math.inverse(mat_global);
    
        // Convert the matrix into vector form
        for (i = 0; i < 3; i++) {
            for (j = 0; j < 3; j++) {
                x2_global[i * 3 + j] = mat2_global[i][j];
            }
        }
    };
    
    // First computation of the transformation matrix
    updateTransformationMatrix();
    
    // Store the transformation vectors in functions
    // in order to make the JSXGraph transformation dynamic
    var x_fcts = [],
        x2_fcts = [];
    for (let i = 0; i < 9; i++) {
        x_fcts[i] = () => x_global[i];
        x2_fcts[i] = () => x2_global[i];
    }
    
    // Create the transform from p1 to p2 and its inverse.
    var transform = board1.create('transform', x_fcts, { type: 'generic' });
    var transform2 = board2.create('transform', x2_fcts, { type: 'generic' });
    
    // Whenever a point of p1 is dragged, the transformation matrix and its 
    // inverse will be updated.
    // Whenever a point of p1 is dragged, the transformation matrix and its 
    // inverse will be updated.
    for (let i = 0; i < 4; i++) {
        p1.vertices[i].on('drag', updateTransformationMatrix);
    }
    
    var player = { fillColor: 'white', strokeColor: 'black', size: 6 };
    
    // Set a point in the camera image
    var A = board1.create('point', [-2.5, 0], player);
    
    // Create its image and a vertical line through the point in the model
    var A2 = board2.create('point', [A, transform], player);
    var line1 = board2.create('segment', [
                [() => A2.X(), -4],
                [() => A2.X(), 4]], { strokeColor: 'yellow' });
    
    // Transform the point and the line into the TV screen
    var A3 = board3.create('point', [A2, transform2], player);
    
    var line2 = board3.create('segment', [line1, transform2], { strokeColor: 'yellow' });
    
 </script> 
/*
This example is licensed under a 
Creative Commons Attribution ShareAlike 4.0 International License.
https://creativecommons.org/licenses/by-sa/4.0/

Please note you have to mention 
The Center of Mobile Learning with Digital Technology
in the credits.
*/

const BOARDID0 = 'your_div_id_0'; // Insert your 1st board id here!
const BOARDID1 = 'your_div_id_1'; // Insert your 2nd board id here!
const BOARDID2 = 'your_div_id_2'; // Insert your 3rd board id here!

const board1 = JXG.JSXGraph.initBoard(BOARDID0, { boundingbox: [-10, 10, 10, -10] });
const board2 = JXG.JSXGraph.initBoard(BOARDID1, { boundingbox: [-10, 10, 10, -10] });
const board3 = JXG.JSXGraph.initBoard(BOARDID2, { boundingbox: [-10, 10, 10, -10] });

board1.addChild(board2);
board1.addChild(board3);

// Polygon in camera image
var p1 = board1.create('polygon', [[-8, -6], [9, -6], [5, 6.5], [-6, 7]], { fillColor: 'green' });

// Polygon in model view
var p2 = board2.create('polygon', [[-5, -4], [5, -4], [5, 4], [-5, 4]], {
    fillColor: 'green',
    vertices: { visible: false, fixed: true }
});

// Polygon on TV screen (copy of p1)
var p3 = board3.create('polygon', [
         [() => p1.vertices[0].X(), () => p1.vertices[0].Y()],
         [() => p1.vertices[1].X(), () => p1.vertices[1].Y()],
         [() => p1.vertices[2].X(), () => p1.vertices[2].Y()],
         [() => p1.vertices[3].X(), () => p1.vertices[3].Y()]
     ], { fillColor: 'green', vertices: { visible: false } });

//
// Compute a projective transformation which maps the polygon p1 to the polygon p2.
//    

// Two global variables containing the transformation matrix (in vector and in matrix form)
var x_global = [],
    x2_global = [],
    mat_global = [[0, 0, 0], [0, 0, 0], [0, 0, 0]],
    mat2_global = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];

// This function computes the transformation matrix
var updateTransformationMatrix = function() {
    var i, j, k, M = [];

    // Initialise a 13x13 matrix to zero.
    for (i = 0; i < 13; i++) {
        M.push([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
    }

    // Set up the system of linear equations:
    // 12 equations and 13 unknowns for the matrix 
    // mat_global such that
    // mat_global * p1 - p2 * (i, j, k, l)^T = 0
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 3; j++) {
            for (k = 0; k < 4; k++) {
                M[i * 4 + k][i * 3 + j] = p1.vertices[k].coords.usrCoords[j];
            }
        }
    }
    for (i = 0; i < 3; i++) {
        for (k = 0; k < 4; k++) {
            M[i * 4 + k][9 + k] = -p2.vertices[k].coords.usrCoords[i];
        }
    }
    // Equation 13: set mat_global[0][0] = 1.
    // Remember that in JSXGraph the coordinates are ordered by (z, x, y)
    M[12][0] = 1;

    // Right hand side vector
    var b = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];

    // Solve the system
    x_global = JXG.Math.Numerics.Gauss(M, b);

    // Convert the solution vector into matrix form
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 3; j++) {
            mat_global[i][j] = x_global[i * 3 + j];
        }
    }

    // Invert the matrix to get the inverse transform
    mat2_global = JXG.Math.inverse(mat_global);

    // Convert the matrix into vector form
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 3; j++) {
            x2_global[i * 3 + j] = mat2_global[i][j];
        }
    }
};

// First computation of the transformation matrix
updateTransformationMatrix();

// Store the transformation vectors in functions
// in order to make the JSXGraph transformation dynamic
var x_fcts = [],
    x2_fcts = [];
for (let i = 0; i < 9; i++) {
    x_fcts[i] = () => x_global[i];
    x2_fcts[i] = () => x2_global[i];
}

// Create the transform from p1 to p2 and its inverse.
var transform = board1.create('transform', x_fcts, { type: 'generic' });
var transform2 = board2.create('transform', x2_fcts, { type: 'generic' });

// Whenever a point of p1 is dragged, the transformation matrix and its 
// inverse will be updated.
// Whenever a point of p1 is dragged, the transformation matrix and its 
// inverse will be updated.
for (let i = 0; i < 4; i++) {
    p1.vertices[i].on('drag', updateTransformationMatrix);
}

var player = { fillColor: 'white', strokeColor: 'black', size: 6 };

// Set a point in the camera image
var A = board1.create('point', [-2.5, 0], player);

// Create its image and a vertical line through the point in the model
var A2 = board2.create('point', [A, transform], player);
var line1 = board2.create('segment', [
            [() => A2.X(), -4],
            [() => A2.X(), 4]], { strokeColor: 'yellow' });

// Transform the point and the line into the TV screen
var A3 = board3.create('point', [A2, transform2], player);

var line2 = board3.create('segment', [line1, transform2], { strokeColor: 'yellow' });
<jsxgraph width="30%, 30%, 30%" aspect-ratio="4 / 3, 4 / 3, 4 / 3" numberOfBoards="3" title="Soccer offside line" description="This construction was copied from JSXGraph examples database: BTW HERE SHOULD BE A GENERATED LINKuseGlobalJS="false">
   /*
   This example is licensed under a 
   Creative Commons Attribution ShareAlike 4.0 International License.
   https://creativecommons.org/licenses/by-sa/4.0/
   
   Please note you have to mention 
   The Center of Mobile Learning with Digital Technology
   in the credits.
   */
   
   const board1 = JXG.JSXGraph.initBoard(BOARDID0, { boundingbox: [-10, 10, 10, -10] });
   const board2 = JXG.JSXGraph.initBoard(BOARDID1, { boundingbox: [-10, 10, 10, -10] });
   const board3 = JXG.JSXGraph.initBoard(BOARDID2, { boundingbox: [-10, 10, 10, -10] });
   
   board1.addChild(board2);
   board1.addChild(board3);
   
   // Polygon in camera image
   var p1 = board1.create('polygon', [[-8, -6], [9, -6], [5, 6.5], [-6, 7]], { fillColor: 'green' });
   
   // Polygon in model view
   var p2 = board2.create('polygon', [[-5, -4], [5, -4], [5, 4], [-5, 4]], {
       fillColor: 'green',
       vertices: { visible: false, fixed: true }
   });
   
   // Polygon on TV screen (copy of p1)
   var p3 = board3.create('polygon', [
            [() => p1.vertices[0].X(), () => p1.vertices[0].Y()],
            [() => p1.vertices[1].X(), () => p1.vertices[1].Y()],
            [() => p1.vertices[2].X(), () => p1.vertices[2].Y()],
            [() => p1.vertices[3].X(), () => p1.vertices[3].Y()]
        ], { fillColor: 'green', vertices: { visible: false } });
   
   //
   // Compute a projective transformation which maps the polygon p1 to the polygon p2.
   //    
   
   // Two global variables containing the transformation matrix (in vector and in matrix form)
   var x_global = [],
       x2_global = [],
       mat_global = [[0, 0, 0], [0, 0, 0], [0, 0, 0]],
       mat2_global = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];
   
   // This function computes the transformation matrix
   var updateTransformationMatrix = function() {
       var i, j, k, M = [];
   
       // Initialise a 13x13 matrix to zero.
       for (i = 0; i < 13; i++) {
           M.push([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
       }
   
       // Set up the system of linear equations:
       // 12 equations and 13 unknowns for the matrix 
       // mat_global such that
       // mat_global * p1 - p2 * (i, j, k, l)^T = 0
       for (i = 0; i < 3; i++) {
           for (j = 0; j < 3; j++) {
               for (k = 0; k < 4; k++) {
                   M[i * 4 + k][i * 3 + j] = p1.vertices[k].coords.usrCoords[j];
               }
           }
       }
       for (i = 0; i < 3; i++) {
           for (k = 0; k < 4; k++) {
               M[i * 4 + k][9 + k] = -p2.vertices[k].coords.usrCoords[i];
           }
       }
       // Equation 13: set mat_global[0][0] = 1.
       // Remember that in JSXGraph the coordinates are ordered by (z, x, y)
       M[12][0] = 1;
   
       // Right hand side vector
       var b = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];
   
       // Solve the system
       x_global = JXG.Math.Numerics.Gauss(M, b);
   
       // Convert the solution vector into matrix form
       for (i = 0; i < 3; i++) {
           for (j = 0; j < 3; j++) {
               mat_global[i][j] = x_global[i * 3 + j];
           }
       }
   
       // Invert the matrix to get the inverse transform
       mat2_global = JXG.Math.inverse(mat_global);
   
       // Convert the matrix into vector form
       for (i = 0; i < 3; i++) {
           for (j = 0; j < 3; j++) {
               x2_global[i * 3 + j] = mat2_global[i][j];
           }
       }
   };
   
   // First computation of the transformation matrix
   updateTransformationMatrix();
   
   // Store the transformation vectors in functions
   // in order to make the JSXGraph transformation dynamic
   var x_fcts = [],
       x2_fcts = [];
   for (let i = 0; i < 9; i++) {
       x_fcts[i] = () => x_global[i];
       x2_fcts[i] = () => x2_global[i];
   }
   
   // Create the transform from p1 to p2 and its inverse.
   var transform = board1.create('transform', x_fcts, { type: 'generic' });
   var transform2 = board2.create('transform', x2_fcts, { type: 'generic' });
   
   // Whenever a point of p1 is dragged, the transformation matrix and its 
   // inverse will be updated.
   // Whenever a point of p1 is dragged, the transformation matrix and its 
   // inverse will be updated.
   for (let i = 0; i < 4; i++) {
       p1.vertices[i].on('drag', updateTransformationMatrix);
   }
   
   var player = { fillColor: 'white', strokeColor: 'black', size: 6 };
   
   // Set a point in the camera image
   var A = board1.create('point', [-2.5, 0], player);
   
   // Create its image and a vertical line through the point in the model
   var A2 = board2.create('point', [A, transform], player);
   var line1 = board2.create('segment', [
               [() => A2.X(), -4],
               [() => A2.X(), 4]], { strokeColor: 'yellow' });
   
   // Transform the point and the line into the TV screen
   var A3 = board3.create('point', [A2, transform2], player);
   
   var line2 = board3.create('segment', [line1, transform2], { strokeColor: 'yellow' });
   
</jsxgraph>

Soccer offside line

A TV camera broadcasting a soccer game "sees" a general quadrangle, not a rectangle. The question is how to add an offside line at the correct position? This is done with a little bit of Linear Algebra. __Left__ is the image the camera sees, the __middle__ is the model, __right__ is the image broadcasted to the TV screens
// Define the ids of your boards in BOARDID0, BOARDID1,...

const board1 = JXG.JSXGraph.initBoard(BOARDID0, { boundingbox: [-10, 10, 10, -10] });
const board2 = JXG.JSXGraph.initBoard(BOARDID1, { boundingbox: [-10, 10, 10, -10] });
const board3 = JXG.JSXGraph.initBoard(BOARDID2, { boundingbox: [-10, 10, 10, -10] });

board1.addChild(board2);
board1.addChild(board3);

// Polygon in camera image
var p1 = board1.create('polygon', [[-8, -6], [9, -6], [5, 6.5], [-6, 7]], { fillColor: 'green' });

// Polygon in model view
var p2 = board2.create('polygon', [[-5, -4], [5, -4], [5, 4], [-5, 4]], {
    fillColor: 'green',
    vertices: { visible: false, fixed: true }
});

// Polygon on TV screen (copy of p1)
var p3 = board3.create('polygon', [
         [() => p1.vertices[0].X(), () => p1.vertices[0].Y()],
         [() => p1.vertices[1].X(), () => p1.vertices[1].Y()],
         [() => p1.vertices[2].X(), () => p1.vertices[2].Y()],
         [() => p1.vertices[3].X(), () => p1.vertices[3].Y()]
     ], { fillColor: 'green', vertices: { visible: false } });

//
// Compute a projective transformation which maps the polygon p1 to the polygon p2.
//    

// Two global variables containing the transformation matrix (in vector and in matrix form)
var x_global = [],
    x2_global = [],
    mat_global = [[0, 0, 0], [0, 0, 0], [0, 0, 0]],
    mat2_global = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];

// This function computes the transformation matrix
var updateTransformationMatrix = function() {
    var i, j, k, M = [];

    // Initialise a 13x13 matrix to zero.
    for (i = 0; i < 13; i++) {
        M.push([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
    }

    // Set up the system of linear equations:
    // 12 equations and 13 unknowns for the matrix 
    // mat_global such that
    // mat_global * p1 - p2 * (i, j, k, l)^T = 0
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 3; j++) {
            for (k = 0; k < 4; k++) {
                M[i * 4 + k][i * 3 + j] = p1.vertices[k].coords.usrCoords[j];
            }
        }
    }
    for (i = 0; i < 3; i++) {
        for (k = 0; k < 4; k++) {
            M[i * 4 + k][9 + k] = -p2.vertices[k].coords.usrCoords[i];
        }
    }
    // Equation 13: set mat_global[0][0] = 1.
    // Remember that in JSXGraph the coordinates are ordered by (z, x, y)
    M[12][0] = 1;

    // Right hand side vector
    var b = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];

    // Solve the system
    x_global = JXG.Math.Numerics.Gauss(M, b);

    // Convert the solution vector into matrix form
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 3; j++) {
            mat_global[i][j] = x_global[i * 3 + j];
        }
    }

    // Invert the matrix to get the inverse transform
    mat2_global = JXG.Math.inverse(mat_global);

    // Convert the matrix into vector form
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 3; j++) {
            x2_global[i * 3 + j] = mat2_global[i][j];
        }
    }
};

// First computation of the transformation matrix
updateTransformationMatrix();

// Store the transformation vectors in functions
// in order to make the JSXGraph transformation dynamic
var x_fcts = [],
    x2_fcts = [];
for (let i = 0; i < 9; i++) {
    x_fcts[i] = () => x_global[i];
    x2_fcts[i] = () => x2_global[i];
}

// Create the transform from p1 to p2 and its inverse.
var transform = board1.create('transform', x_fcts, { type: 'generic' });
var transform2 = board2.create('transform', x2_fcts, { type: 'generic' });

// Whenever a point of p1 is dragged, the transformation matrix and its 
// inverse will be updated.
// Whenever a point of p1 is dragged, the transformation matrix and its 
// inverse will be updated.
for (let i = 0; i < 4; i++) {
    p1.vertices[i].on('drag', updateTransformationMatrix);
}

var player = { fillColor: 'white', strokeColor: 'black', size: 6 };

// Set a point in the camera image
var A = board1.create('point', [-2.5, 0], player);

// Create its image and a vertical line through the point in the model
var A2 = board2.create('point', [A, transform], player);
var line1 = board2.create('segment', [
            [() => A2.X(), -4],
            [() => A2.X(), 4]], { strokeColor: 'yellow' });

// Transform the point and the line into the TV screen
var A3 = board3.create('point', [A2, transform2], player);

var line2 = board3.create('segment', [line1, transform2], { strokeColor: 'yellow' });

license

This example is licensed under a Creative Commons Attribution ShareAlike 4.0 International License.
Please note you have to mention The Center of Mobile Learning with Digital Technology in the credits.