      1 /*
      2     Copyright 2008-2025
      3         Matthias Ehmann,
      4         Michael Gerhaeuser,
      5         Carsten Miller,
      6         Andreas Walter,
      7         Alfred Wassermann
      8
      9     This file is part of JSXGraph.
     10
     11     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
     12
     13     You can redistribute it and/or modify it under the terms of the
     14
     15       * GNU Lesser General Public License as published by
     16         the Free Software Foundation, either version 3 of the License, or
     17         (at your option) any later version
     18       OR
     19       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
     20
     21     JSXGraph is distributed in the hope that it will be useful,
     22     but WITHOUT ANY WARRANTY; without even the implied warranty of
     23     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     24     GNU Lesser General Public License for more details.
     25
     26     You should have received a copy of the GNU Lesser General Public License and
     27     the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/>
     28     and <https://opensource.org/licenses/MIT/>.
     29  */
     30 /*
     31     Some functionalities in this file were developed as part of a software project
     32     with students. We would like to thank all contributors for their help:
     33
     34     Winter semester 2023/2024:
     35         Timm Braun
     36         Nina Koch
     37  */
     38
     39 import JXG from "../jxg.js";
     40 import Mat from "../math/math.js";
     41 import Type from "../utils/type.js";
     42 import Const from "../base/constants.js";
     43
     44 /**
     45  * @class A grid is a mesh consisting of vertical and horizontal lines or other geometrical objects.
     46  * @pseudo
     47  * @description A grid is a set of vertical and horizontal lines or other geometrical objects (faces)
     48  * to support the user with element placement or to improve determination of position.
     49  * This method takes up to two facultative parent elements. These are used to set distance between
     50  * grid elements in case of attribute <tt>majorStep</tt> or <tt>minorElements</tt> is set to 'auto'.
     51  * Then the major/minor grid element distance is set to the ticks distance of parent axes.
     52  * It is usually instantiated on the board's creation via the attribute <tt>grid</tt> set to true.
     53  * @constructor
     54  * @name Grid
     55  * @type JXG.Curve
     56  * @augments JXG.Curve
     57  * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     58  * @param {JXG.Axis_JXG.Axis} a1,a2 Optional parent axis.
     59  *
     60  * @example
     61  * // standard grid
     62  * var g = board.create('grid', [], {});
     63  * </pre><div id="JXGc8dde3f5-22ef-4c43-9505-34b299b5b24d" class="jxgbox" style="width: 300px; height: 300px;"></div>
     64  * <script type="text/javascript">
     65  *  (function() {
     66  *      var board = JXG.JSXGraph.initBoard('JXGc8dde3f5-22ef-4c43-9505-34b299b5b24d',
     67  *          {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false});
     68  *      var g = board.create('grid', [], {});
     69  *  })();
     70  * </script><pre>
     71  *
     72  * @example
     73  * // more fancy grid
     74  * var g = board.create('grid', [], {
     75  *     major: {
     76  *         face: 'plus',
     77  *         size: 7,
     78  *         strokeColor: 'green',
     79  *         strokeOpacity: 1,
     80  *     },
     81  *     minor: {
     82  *         size: 4
     83  *     },
     84  *     minorElements: 3,
     85  * });
     86  * </pre><div id="JXG02374171-b27c-4ccc-a14a-9f5bd1162623" class="jxgbox" style="width: 300px; height: 300px;"></div>
     87  * <script type="text/javascript">
     88  *     (function() {
     89  *         var board = JXG.JSXGraph.initBoard('JXG02374171-b27c-4ccc-a14a-9f5bd1162623',
     90  *             {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false});
     91  *         var g = board.create('grid', [], {
     92  *             major: {
     93  *                 face: 'plus',
     94  *                 size: 7,
     95  *                 strokeColor: 'green',
     96  *                 strokeOpacity: 1,
     97  *             },
     98  *             minor: {
     99  *                 size: 4
    100  *             },
    101  *             minorElements: 3,
    102  *         });
    103  *     })();
    104  * </script><pre>
    105  *
    106  * @example
    107  * // extreme fancy grid
    108  * var grid = board.create('grid', [], {
    109  *     major: {
    110  *         face: 'regularPolygon',
    111  *         size: 8,
    112  *         strokeColor: 'blue',
    113  *         fillColor: 'orange',
    114  *         strokeOpacity: 1,
    115  *     },
    116  *     minor: {
    117  *         face: 'diamond',
    118  *         size: 4,
    119  *         strokeColor: 'green',
    120  *         fillColor: 'grey',
    121  *     },
    122  *     minorElements: 1,
    123  *     includeBoundaries: false,
    124  * });
    125  * </pre><div id="JXG00f3d068-093c-4c1d-a1ab-96c9ee73c173" class="jxgbox" style="width: 300px; height: 300px;"></div>
    126  * <script type="text/javascript">
    127  *     (function() {
    128  *         var board = JXG.JSXGraph.initBoard('JXG00f3d068-093c-4c1d-a1ab-96c9ee73c173',
    129  *             {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false});
    130  *         var grid = board.create('grid', [], {
    131  *             major: {
    132  *                 face: 'regularPolygon',
    133  *                 size: 8,
    134  *                 strokeColor: 'blue',
    135  *                 fillColor: 'orange',
    136  *                 strokeOpacity: 1,
    137  *             },
    138  *             minor: {
    139  *                 face: 'diamond',
    140  *                 size: 4,
    141  *                 strokeColor: 'green',
    142  *                 fillColor: 'grey',
    143  *             },
    144  *             minorElements: 1,
    145  *             includeBoundaries: false,
    146  *         });
    147  *     })();
    148  * </script><pre>
    149  *
    150  * @example
    151  * // grid with parent axes
    152  * var axis1 = board.create('axis', [[-1, -2.5], [1, -2.5]], {
    153  *     ticks: {
    154  *         strokeColor: 'green',
    155  *         strokeWidth: 2,
    156  *         minorticks: 2,
    157  *         majorHeight: 10,
    158  *         drawZero: true
    159  *     }
    160  * });
    161  * var axis2 = board.create('axis', [[3, 0], [3, 2]], {
    162  *     ticks: {
    163  *         strokeColor: 'red',
    164  *         strokeWidth: 2,
    165  *         minorticks: 3,
    166  *         majorHeight: 10,
    167  *         drawZero: true
    168  *     }
    169  * });
    170  * var grid = board.create('grid', [axis1, axis2], {
    171  *     major: {
    172  *         face: 'line'
    173  *     },
    174  *     minor: {
    175  *         face: 'point',
    176  *         size: 3
    177  *     },
    178  *     minorElements: 'auto',
    179  *     includeBoundaries: false,
    180  * });
    181  * </pre><div id="JXG0568e385-248c-43a9-87ed-07aceb8cc3ab" class="jxgbox" style="width: 300px; height: 300px;"></div>
    182  * <script type="text/javascript">
    183  *     (function() {
    184  *         var board = JXG.JSXGraph.initBoard('JXG0568e385-248c-43a9-87ed-07aceb8cc3ab',
    185  *             {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false});
    186  *         var axis1 = board.create('axis', [[-1, -2.5], [1, -2.5]], {
    187  *             ticks: {
    188  *                 strokeColor: 'green',
    189  *                 strokeWidth: 2,
    190  *                 minorticks: 2,
    191  *                 majorHeight: 10,
    192  *                 drawZero: true
    193  *             }
    194  *         });
    195  *         var axis2 = board.create('axis', [[3, 0], [3, 2]], {
    196  *             ticks: {
    197  *                 strokeColor: 'red',
    198  *                 strokeWidth: 2,
    199  *                 minorticks: 3,
    200  *                 majorHeight: 10,
    201  *                 drawZero: true
    202  *             }
    203  *         });
    204  *         var grid = board.create('grid', [axis1, axis2], {
    205  *             major: {
    206  *                 face: 'line',
    207  *             },
    208  *             minor: {
    209  *                 face: 'point',
    210  *                 size: 3
    211  *             },
    212  *             minorElements: 'auto',
    213  *             includeBoundaries: false,
    214  *         });
    215  *     }());
    216  * </script><pre>
    217  */
    218 JXG.createGrid = function (board, parents, attributes) {
    219     var eps = Mat.eps,       // to avoid rounding errors
    220         maxLines = 5000,    // maximum number of vertical or horizontal grid elements (abort criterion for performance reasons)
    221
    222         majorGrid,      // main object which will be returned as grid
    223         minorGrid,      // sub-object
    224         parentAxes,     // {Array} array of user defined axes (allowed length 0, 1 or 2)
    225
    226         attrGrid,       // attributes for grid
    227         attrMajor,      // attributes for major grid
    228         attrMinor,      // attributes for minor grid
    229
    230         majorStep,      // {[Number]} distance (in usrCoords) in x- and y-direction between center of two major grid elements
    231         majorSize = [],
    232         majorRadius = [], // half of the size of major grid element
    233
    234         createDataArrayForFace;  // {Function}
    235
    236     parentAxes = parents;
    237     if (
    238         parentAxes.length > 2 ||
    239         (parentAxes.length >= 1 && parentAxes[0].elType !== 'axis') ||
    240         (parentAxes.length >= 2 && parentAxes[1].elType !== 'axis')
    241     ) {
    242         throw new Error(
    243             "JSXGraph: Can't create 'grid' with parent type '" +
    244             parents[0].elType +
    245             "'. Possible parent types: [axis,axis]"
    246         );
    247     }
    248     if (!Type.exists(parentAxes[0]) && Type.exists(board.defaultAxes)) {
    249         parentAxes[0] = board.defaultAxes.x;
    250     }
    251     if (!Type.exists(parentAxes[1]) && Type.exists(board.defaultAxes)) {
    252         parentAxes[1] = board.defaultAxes.y;
    253     }
    254
    255     /**
    256      * Creates for each face the right data array for updateDataArray function.
    257      * This functions also adapts visProps according to face.
    258
    259      * @param {String} face Chosen face to be drawn
    260      * @param {Object} grid Curve/grid to be drawn
    261      * @param {Number} x x-coordinate of target position
    262      * @param {Number} y y-coordinate of target position
    263      * @param {Number} radiusX Half of width in x-direction of face to be drawn
    264      * @param {Number} radiusY Half of width in y-direction of face to be drawn
    265      * @param {Array} bbox boundingBox
    266      *
    267      * @returns {Array} data array of length 2 (x- and y- coordinated for curve)
    268      * @private
    269      * @ignore
    270      */
    271     createDataArrayForFace = function (face, grid, x, y, radiusX, radiusY, bbox) {
    272         var t, q, m, n, array, rx2, ry2;
    273
    274         switch (face.toLowerCase()) {
    275
    276             // filled point
    277             case '.':
    278             case 'point':
    279                 grid.visProp.linecap = 'round';
    280                 grid.visProp.strokewidth = radiusX * grid.board.unitX + radiusY * grid.board.unitY;
    281                 return [
    282                     [x, x, NaN],
    283                     [y, y, NaN]
    284                 ];
    285
    286             // bezierCircle
    287             case 'o':
    288             case 'circle':
    289                 grid.visProp.linecap = 'square';
    290                 grid.bezierDegree = 3;
    291                 q = 4 * Math.tan(Math.PI / 8) / 3;
    292                 return [
    293                     [
    294                         x + radiusX, x + radiusX, x + q * radiusX, x,
    295                         x - q * radiusX, x - radiusX, x - radiusX, x - radiusX,
    296                         x - q * radiusX, x, x + q * radiusX, x + radiusX,
    297                         x + radiusX, NaN
    298                     ], [
    299                         y, y + q * radiusY, y + radiusY, y + radiusY,
    300                         y + radiusY, y + q * radiusY, y, y - q * radiusY,
    301                         y - radiusY, y - radiusY, y - radiusY, y - q * radiusY,
    302                         y, NaN
    303                     ]
    304                 ];
    305
    306             // polygon
    307             case 'regpol':
    308             case 'regularpolygon':
    309                 grid.visProp.linecap = 'round';
    310                 n = grid.evalVisProp('polygonvertices');
    311                 array = [[], []];
    312                 // approximation of circle with variable n
    313                 for (t = 0; t <= 2 * Math.PI; t += (2 * Math.PI) / n) {
    314                     array[0].push(x - radiusX * Math.sin(t));
    315                     array[1].push(y - radiusY * Math.cos(t));
    316                 }
    317                 array[0].push(NaN);
    318                 array[1].push(NaN);
    319                 return array;
    320
    321             // square
    322             case '[]':
    323             case 'square':
    324                 grid.visProp.linecap = 'square';
    325                 return [
    326                     [x - radiusX, x + radiusX, x + radiusX, x - radiusX, x - radiusX, NaN],
    327                     [y + radiusY, y + radiusY, y - radiusY, y - radiusY, y + radiusY, NaN]
    328                 ];
    329
    330             // diamond
    331             case '<>':
    332             case 'diamond':
    333                 grid.visProp.linecap = 'square';
    334                 return [
    335                     [x, x + radiusX, x, x - radiusX, x, NaN],
    336                     [y + radiusY, y, y - radiusY, y, y + radiusY, NaN]
    337                 ];
    338
    339             // diamond2
    340             case '<<>>':
    341             case 'diamond2':
    342                 grid.visProp.linecap = 'square';
    343                 rx2 = radiusX * Math.sqrt(2);
    344                 ry2 = radiusY * Math.sqrt(2);
    345                 return [
    346                     [x, x + rx2, x, x - rx2, x, NaN],
    347                     [y + ry2, y, y - ry2, y, y + ry2, NaN]
    348                 ];
    349
    350             case 'x':
    351             case 'cross':
    352                 return [
    353                     [x - radiusX, x + radiusX, NaN, x - radiusX, x + radiusX, NaN],
    354                     [y + radiusY, y - radiusY, NaN, y - radiusY, y + radiusY, NaN]
    355                 ];
    356
    357             case '+':
    358             case 'plus':
    359                 return [
    360                     [x - radiusX, x + radiusX, NaN, x, x, NaN],
    361                     [y, y, NaN, y - radiusY, y + radiusY, NaN]
    362                 ];
    363
    364             case '-':
    365             case 'minus':
    366                 return [
    367                     [x - radiusX, x + radiusX, NaN],
    368                     [y, y, NaN]
    369                 ];
    370
    371             case '|':
    372             case 'divide':
    373                 return [
    374                     [x, x, NaN],
    375                     [y - radiusY, y + radiusY, NaN]
    376                 ];
    377
    378             case '^':
    379             case 'a':
    380             case 'A':
    381             case 'triangleup':
    382                 return [
    383                     [x - radiusX, x, x + radiusX, NaN],
    384                     [y - radiusY, y, y - radiusY, NaN]
    385                 ];
    386
    387             case 'v':
    388             case 'triangledown':
    389                 return [
    390                     [x - radiusX, x, x + radiusX, NaN],
    391                     [y + radiusY, y, y + radiusY, NaN]
    392                 ];
    393
    394             case '<':
    395             case 'triangleleft':
    396                 return [
    397                     [x + radiusX, x, x + radiusX, NaN],
    398                     [y + radiusY, y, y - radiusY, NaN]
    399                 ];
    400
    401             case '>':
    402             case 'triangleright':
    403                 return [
    404                     [x - radiusX, x, x - radiusX, NaN],
    405                     [y + radiusY, y, y - radiusY, NaN]
    406                 ];
    407
    408             case 'line':
    409                 m = grid.evalVisProp('margin');
    410                 return [
    411                     // [x, x, NaN, bbox[0] + (4 / grid.board.unitX), bbox[2] - (4 / grid.board.unitX), NaN],
    412                     [x, x, NaN, bbox[0] - m / grid.board.unitX, bbox[2] + m / grid.board.unitX, NaN],
    413                     [bbox[1] + m / grid.board.unitY, bbox[3] - m / grid.board.unitY, NaN, y, y, NaN]
    414                 ];
    415
    416             default:
    417                 return [[], []];
    418         }
    419     };
    420
    421     // Themes
    422     attrGrid = Type.copyAttributes(attributes, board.options, 'grid');
    423     Type.mergeAttr(attrGrid, attrGrid.themes[attrGrid.theme], false);
    424
    425     // Create majorGrid
    426     attrMajor = {};
    427     Type.mergeAttr(attrMajor, attrGrid, true, true);
    428     Type.mergeAttr(attrMajor, attrGrid.major, true, true);
    429     majorGrid = board.create('curve', [[null], [null]], attrMajor);
    430     majorGrid.elType = 'grid';
    431     majorGrid.type = Const.OBJECT_TYPE_GRID;
    432
    433     // Create minorGrid
    434     attrMinor = {};
    435     Type.mergeAttr(attrMinor, attrGrid, true, true);
    436     Type.mergeAttr(attrMinor, attrGrid.minor, true, true);
    437     if (attrMinor.id === attrMajor.id) {
    438         attrMinor.id = majorGrid.id + '_minor';
    439     }
    440     if (attrMinor.name === attrMajor.name) {
    441         attrMinor.name = majorGrid.name + '_minor';
    442     }
    443     minorGrid = board.create('curve', [[null], [null]], attrMinor);
    444     minorGrid.elType = 'grid';
    445     minorGrid.type = Const.OBJECT_TYPE_GRID;
    446
    447     majorGrid.minorGrid = minorGrid;
    448     minorGrid.majorGrid = majorGrid;
    449
    450     majorGrid.hasPoint = function () { return false; };
    451     minorGrid.hasPoint = function () { return false; };
    452
    453     majorGrid.inherits.push(minorGrid);
    454
    455     majorGrid.updateDataArray = function () {
    456         var bbox = this.board.getBoundingBox(),
    457             startX, startY,
    458             x, y, m,
    459             dataArr,
    460             finite, delta,
    461
    462             gridX = this.evalVisProp('gridx'), // for backwards compatibility
    463             gridY = this.evalVisProp('gridy'), // for backwards compatibility
    464             face = this.evalVisProp('face'),
    465             drawZero = this.evalVisProp('drawzero'),
    466             drawZeroOrigin = drawZero === true || (Type.isObject(drawZero) && this.eval(drawZero.origin) === true),
    467             drawZeroX = drawZero === true || (Type.isObject(drawZero) && this.eval(drawZero.x) === true),
    468             drawZeroY = drawZero === true || (Type.isObject(drawZero) && this.eval(drawZero.y) === true),
    469
    470             includeBoundaries = this.evalVisProp('includeboundaries'),
    471             forceSquare = this.evalVisProp('forcesquare');
    472
    473         this.dataX = [];
    474         this.dataY = [];
    475
    476         // set global majorStep
    477         majorStep = this.evalVisProp('majorstep');
    478         if (!Type.isArray(majorStep)) {
    479             majorStep = [majorStep, majorStep];
    480         }
    481         if (majorStep.length < 2) {
    482             majorStep = [majorStep[0], majorStep[0]];
    483         }
    484         if (Type.exists(gridX)) {
    485             JXG.deprecated("gridX", "majorStep");
    486             majorStep[0] = gridX;
    487         }
    488         if (Type.exists(gridY)) {
    489             JXG.deprecated("gridY", "majorStep");
    490             majorStep[1] = gridY;
    491         }
    492
    493         if (majorStep[0] === 'auto') {
    494             // majorStep[0] = 1; // parentAxes[0] may not be defined
    495             // Prevent too many grid lines if majorstep:'auto'
    496             delta = Math.pow(10, Math.floor(Math.log(50 / this.board.unitX) / Math.LN10));
    497             majorStep[0] = delta;
    498
    499             if (Type.exists(parentAxes[0])) {
    500                 majorStep[0] = parentAxes[0].ticks[0].getDistanceMajorTicks();
    501             }
    502         } else {
    503             // This allows the value to have unit px, abs, % or fr.
    504             majorStep[0] = Type.parseNumber(majorStep[0], Math.abs(bbox[1] - bbox[3]), 1 / this.board.unitX);
    505         }
    506
    507         if (majorStep[1] === 'auto') {
    508             // majorStep[1] = 1; // parentAxes[1] may not be defined
    509             // Prevent too many grid lines if majorstep:'auto'
    510             delta = Math.pow(10, Math.floor(Math.log(50 / this.board.unitY) / Math.LN10));
    511             majorStep[1] = delta;
    512
    513             if (Type.exists(parentAxes[1])) {
    514                 majorStep[1] = parentAxes[1].ticks[0].getDistanceMajorTicks();
    515             }
    516         } else {
    517             // This allows the value to have unit px, abs, % or fr.
    518             majorStep[1] = Type.parseNumber(majorStep[1], Math.abs(bbox[0] - bbox[2]), 1 / this.board.unitY);
    519         }
    520
    521         if (forceSquare === 'min' || forceSquare === true) {
    522             if (majorStep[0] * this.board.unitX <= majorStep[1] * this.board.unitY) { // compare px-values
    523                 majorStep[1] = majorStep[0] / this.board.unitY * this.board.unitX;
    524             } else {
    525                 majorStep[0] = majorStep[1] / this.board.unitX * this.board.unitY;
    526             }
    527         } else if (forceSquare === 'max') {
    528             if (majorStep[0] * this.board.unitX <= majorStep[1] * this.board.unitY) { // compare px-values
    529                 majorStep[0] = majorStep[1] / this.board.unitX * this.board.unitY;
    530             } else {
    531                 majorStep[1] = majorStep[0] / this.board.unitY * this.board.unitX;
    532             }
    533         }
    534
    535         // Set global majorSize
    536         majorSize = this.evalVisProp('size');
    537         if (!Type.isArray(majorSize)) {
    538             majorSize = [majorSize, majorSize];
    539         }
    540         if (majorSize.length < 2) {
    541             majorSize = [majorSize[0], majorSize[0]];
    542         }
    543
    544         // Here comes a hack:
    545         // "majorsize" is filled by the attribute "size" which is usually considered
    546         // as pixel value. However, usually a number value for size is
    547         // considered to be in pixel, while parseNumber expects user coords.
    548         // Therefore, we have to add 'px'.
    549         if (Type.isNumber(majorSize[0], true)) {
    550             majorSize[0] = majorSize[0] + "px";
    551         }
    552         if (Type.isNumber(majorSize[1], true)) {
    553             majorSize[1] = majorSize[1] + "px";
    554         }
    555         majorSize[0] = Type.parseNumber(majorSize[0], majorStep[0], 1 / this.board.unitX);
    556         majorSize[1] = Type.parseNumber(majorSize[1], majorStep[1], 1 / this.board.unitY);
    557         majorRadius[0] = majorSize[0] / 2;
    558         majorRadius[1] = majorSize[1] / 2;
    559
    560         // calculate start position of curve
    561         startX = Mat.roundToStep(bbox[0], majorStep[0]);
    562         startY = Mat.roundToStep(bbox[1], majorStep[1]);
    563
    564         // check if number of grid elements side by side is not too large
    565         finite = isFinite(startX) && isFinite(startY) &&
    566             isFinite(bbox[2]) && isFinite(bbox[3]) &&
    567             Math.abs(bbox[2]) < Math.abs(majorStep[0] * maxLines) &&
    568             Math.abs(bbox[3]) < Math.abs(majorStep[1] * maxLines);
    569
    570         // POI finite = false means that no grid is drawn. Should we change this?
    571         // Draw grid elements
    572         if (face.toLowerCase() === 'line') {
    573             m = majorGrid.evalVisProp('margin');
    574             for (y = startY; finite && y >= bbox[3]; y -= majorStep[1]) {
    575                 if (
    576                     (!drawZeroOrigin && Math.abs(y) < eps) ||
    577                     (!drawZeroY && Math.abs(y) < eps) ||
    578                     (!includeBoundaries && (
    579                         y <= bbox[3] + majorRadius[1] ||
    580                         y >= bbox[1] - majorRadius[1]
    581                     ))
    582                 ) {
    583                     continue;
    584                 }
    585
    586                 dataArr = [
    587                     [bbox[0] - m / majorGrid.board.unitX, bbox[2] + m / majorGrid.board.unitX, NaN],
    588                     [y, y, NaN]
    589                 ];
    590                 // Push is drastically faster than concat
    591                 Type.concat(this.dataX, dataArr[0]);
    592                 Type.concat(this.dataY, dataArr[1]);
    593             }
    594             for (x = startX; finite && x <= bbox[2]; x += majorStep[0]) {
    595                 if (
    596                     (!drawZeroOrigin && Math.abs(x) < eps) ||
    597                     (!drawZeroX && Math.abs(x) < eps) ||
    598                     (!includeBoundaries && (
    599                         x <= bbox[0] + majorRadius[0] ||
    600                         x >= bbox[2] - majorRadius[0]
    601                     ))
    602                 ) {
    603                     continue;
    604                 }
    605
    606                 dataArr = [
    607                     [x, x, NaN],
    608                     [bbox[1] + m / majorGrid.board.unitY, bbox[3] - m / majorGrid.board.unitY, NaN]
    609                 ];
    610                 // Push is drastically faster than concat
    611                 Type.concat(this.dataX, dataArr[0]);
    612                 Type.concat(this.dataY, dataArr[1]);
    613             }
    614         } else {
    615             for (y = startY; finite && y >= bbox[3]; y -= majorStep[1]) {
    616                 for (x = startX; finite && x <= bbox[2]; x += majorStep[0]) {
    617
    618                     if (
    619                         (!drawZeroOrigin && Math.abs(y) < eps && Math.abs(x) < eps) ||
    620                         (!drawZeroX && Math.abs(y) < eps && Math.abs(x) >= eps) ||
    621                         (!drawZeroY && Math.abs(x) < eps && Math.abs(y) >= eps) ||
    622                         (!includeBoundaries && (
    623                             x <= bbox[0] + majorRadius[0] ||
    624                             x >= bbox[2] - majorRadius[0] ||
    625                             y <= bbox[3] + majorRadius[1] ||
    626                             y >= bbox[1] - majorRadius[1]
    627                         ))
    628                     ) {
    629                         continue;
    630                     }
    631
    632                     dataArr = createDataArrayForFace(face, majorGrid, x, y, majorRadius[0], majorRadius[1], bbox);
    633                     // Push is drastically faster than concat
    634                     Type.concat(this.dataX, dataArr[0]);
    635                     Type.concat(this.dataY, dataArr[1]);
    636                 }
    637             }
    638         }
    639     };
    640
    641     minorGrid.updateDataArray = function () {
    642         var bbox = this.board.getBoundingBox(),
    643             startX, startY,
    644             x, y, m,
    645             dataArr,
    646             finite,
    647
    648             minorStep = [],
    649             minorRadius = [],
    650             XdisTo0, XdisFrom0, YdisTo0, YdisFrom0, // {Number} absolute distances of minor grid elements center to next major grid element center
    651             dis0To, dis1To, dis2To, dis3To,         // {Number} absolute distances of borders of the boundingBox to the next major grid element.
    652             dis0From, dis1From, dis2From, dis3From,
    653
    654             minorElements = this.evalVisProp('minorelements'),
    655             minorSize = this.evalVisProp('size'),
    656             minorFace = this.evalVisProp('face'),
    657             minorDrawZero = this.evalVisProp('drawzero'),
    658             minorDrawZeroX = minorDrawZero === true || (Type.isObject(minorDrawZero) && this.eval(minorDrawZero.x) === true),
    659             minorDrawZeroY = minorDrawZero === true || (Type.isObject(minorDrawZero) && this.eval(minorDrawZero.y) === true),
    660
    661             majorFace = this.majorGrid.evalVisProp('face'),
    662             majorDrawZero = this.majorGrid.evalVisProp('drawzero'),
    663             majorDrawZeroOrigin = majorDrawZero === true || (Type.isObject(majorDrawZero) && this.eval(majorDrawZero.origin) === true),
    664             majorDrawZeroX = majorDrawZero === true || (Type.isObject(majorDrawZero) && this.eval(majorDrawZero.x) === true),
    665             majorDrawZeroY = majorDrawZero === true || (Type.isObject(majorDrawZero) && this.eval(majorDrawZero.y) === true),
    666
    667             includeBoundaries = this.evalVisProp('includeboundaries');
    668
    669         this.dataX = [];
    670         this.dataY = [];
    671
    672         // set minorStep
    673         // minorElements can be 'auto' or a number (also a number like '20')
    674         if (!Type.isArray(minorElements)) {
    675             minorElements = [minorElements, minorElements];
    676         }
    677         if (minorElements.length < 2) {
    678             minorElements = [minorElements[0], minorElements[0]];
    679         }
    680
    681         if (Type.isNumber(minorElements[0], true)) {
    682             minorElements[0] = parseFloat(minorElements[0]);
    683
    684         } else { // minorElements[0]  === 'auto'
    685             minorElements[0] = 3; // parentAxes[0] may not be defined
    686             if (Type.exists(parentAxes[0])) {
    687                 minorElements[0] = parentAxes[0].eval(parentAxes[0].getAttribute('ticks').minorticks);
    688             }
    689         }
    690         minorStep[0] = majorStep[0] / (minorElements[0] + 1);
    691
    692         if (Type.isNumber(minorElements[1], true)) {
    693             minorElements[1] = parseFloat(minorElements[1]);
    694
    695         } else { // minorElements[1] === 'auto'
    696             minorElements[1] = 3; // parentAxes[1] may not be defined
    697             if (Type.exists(parentAxes[1])) {
    698                 minorElements[1] = parentAxes[1].eval(parentAxes[1].getAttribute('ticks').minorticks);
    699             }
    700         }
    701         minorStep[1] = majorStep[1] / (minorElements[1] + 1);
    702
    703         // set global minorSize
    704         if (!Type.isArray(minorSize)) {
    705             minorSize = [minorSize, minorSize];
    706         }
    707         if (minorSize.length < 2) {
    708             minorSize = [minorSize[0], minorSize[0]];
    709         }
    710
    711         // minorRadius = [
    712         //     Type.parseNumber(minorSize[0], minorStep[0] * 0.5, 1 / this.board.unitX),
    713         //     Type.parseNumber(minorSize[0], minorStep[0] * 0.5, 1 / this.board.unitY)
    714         // ];
    715
    716         // Here comes a hack:
    717         // "minorsize" is filled by the attribute "size" which is usually considered
    718         // as pixel value. However, usually a number value for size is
    719         // considered to be in pixel, while parseNumber expects user coords.
    720         // Therefore, we have to add 'px'.
    721         if (Type.isNumber(minorSize[0], true)) {
    722             minorSize[0] = minorSize[0] + "px";
    723         }
    724         if (Type.isNumber(minorSize[1], true)) {
    725             minorSize[1] = minorSize[1] + "px";
    726         }
    727         minorSize[0] = Type.parseNumber(minorSize[0], minorStep[0], 1 / this.board.unitX);
    728         minorSize[1] = Type.parseNumber(minorSize[1], minorStep[1], 1 / this.board.unitY);
    729         minorRadius[0] = minorSize[0] * 0.5;
    730         minorRadius[1] = minorSize[1] * 0.5;
    731
    732         // calculate start position of curve
    733         startX = Mat.roundToStep(bbox[0], minorStep[0]);
    734         startY = Mat.roundToStep(bbox[1], minorStep[1]);
    735
    736         // check if number of grid elements side by side is not too large
    737         finite = isFinite(startX) && isFinite(startY) &&
    738             isFinite(bbox[2]) && isFinite(bbox[3]) &&
    739             Math.abs(bbox[2]) <= Math.abs(minorStep[0] * maxLines) &&
    740             Math.abs(bbox[3]) < Math.abs(minorStep[1] * maxLines);
    741
    742         // POI finite = false means that no grid is drawn. Should we change this?
    743
    744         // draw grid elements
    745         if (minorFace.toLowerCase() !== 'line') {
    746             for (y = startY; finite && y >= bbox[3]; y -= minorStep[1]) {
    747                 for (x = startX; finite && x <= bbox[2]; x += minorStep[0]) {
    748
    749                     /* explanation:
    750                          |<___XdisTo0___><___________XdisFrom0___________>
    751                          |                .                .               .
    752                      ____|____            .                .           _________
    753                     |    |    |         ____              ____        |         |
    754                     |    |    |        |    |            |    |       |         |
    755                     |    |    |        |____|            |____|       |         |
    756                     |____|____|           | |              .          |_________|
    757                          |    |           . \              .              .
    758                          |  \             . minorRadius[0]   .              .
    759                          |   majorRadius[0] .                .              .
    760                          |                .                .              .
    761                          |<----------->   .                .              .
    762                          |    \           .                .              .
    763                          |     XdisTo0 - minorRadius[0] <= majorRadius[0] ? -> exclude
    764                          |                .                .              .
    765                          |                .  <--------------------------->
    766                          |                             \
    767                          |                              XdisFrom0 - minorRadius[0] <= majorRadius[0] ? -> exclude
    768                          |
    769                    -——---|————————-————---|----------------|---------------|-------->
    770                          |
    771                          |<______________________majorStep[0]_____________________>
    772                          |
    773                          |<__minorStep[0]____><__minorStep[0]_____><__minorStep[0]_____>
    774                          |
    775                          |
    776                     */
    777                     XdisTo0 = Mat.roundToStep(Math.abs(x), majorStep[0]);
    778                     XdisTo0 = Math.abs(XdisTo0 - Math.abs(x));
    779                     XdisFrom0 = majorStep[0] - XdisTo0;
    780
    781                     YdisTo0 = Mat.roundToStep(Math.abs(y), majorStep[1]);
    782                     YdisTo0 = Math.abs(YdisTo0 - Math.abs(y));
    783                     YdisFrom0 = majorStep[1] - YdisTo0;
    784
    785                     if (majorFace === 'line') {
    786                         // for majorFace 'line' do not draw minor grid elements on lines
    787                         if (
    788                             XdisTo0 - minorRadius[0] - majorRadius[0] < eps ||
    789                             XdisFrom0 - minorRadius[0] - majorRadius[0] < eps ||
    790                             YdisTo0 - minorRadius[1] - majorRadius[1] < eps ||
    791                             YdisFrom0 - minorRadius[1] - majorRadius[1] < eps
    792                         ) {
    793                             continue;
    794                         }
    795
    796                     } else {
    797                         if ((
    798                             XdisTo0 - minorRadius[0] - majorRadius[0] < eps ||
    799                             XdisFrom0 - minorRadius[0] - majorRadius[0] < eps
    800                         ) && (
    801                                 YdisTo0 - minorRadius[1] - majorRadius[1] < eps ||
    802                                 YdisFrom0 - minorRadius[1] - majorRadius[1] < eps
    803                             )) {
    804                             // if major grid elements (on 0 or axes) are not existing, minor grid elements have to exist. Otherwise:
    805                             if ((
    806                                 majorDrawZeroOrigin ||
    807                                 majorRadius[1] - Math.abs(y) + minorRadius[1] < eps ||
    808                                 majorRadius[0] - Math.abs(x) + minorRadius[0] < eps
    809                             ) && (
    810                                     majorDrawZeroX ||
    811                                     majorRadius[1] - Math.abs(y) + minorRadius[1] < eps ||
    812                                     majorRadius[0] + Math.abs(x) - minorRadius[0] < eps
    813                                 ) && (
    814                                     majorDrawZeroY ||
    815                                     majorRadius[0] - Math.abs(x) + minorRadius[0] < eps ||
    816                                     majorRadius[1] + Math.abs(y) - minorRadius[1] < eps
    817                                 )) {
    818                                 continue;
    819                             }
    820                         }
    821                     }
    822                     if (
    823                         (!minorDrawZeroY && Math.abs(x) < eps) ||
    824                         (!minorDrawZeroX && Math.abs(y) < eps)
    825                     ) {
    826                         continue;
    827                     }
    828
    829                     /* explanation of condition below:
    830
    831                           |         __dis2To___> _dis2From_      // dis2To bzw. dis2From >= majorRadius[0]
    832                           |      __/_          \/         _\__
    833                           |     |    |  []     >         |    |
    834                           |     |____|         >         |____|
    835                           |                    >
    836                           |                    >
    837                           |    x-minorSize[0]  > bbox[2]
    838                           0               .    >/
    839                        -——|————————-————.-.——.—>
    840                           |             . .  . >
    841                           |             . .  . >
    842                           |             . .  . > dis2From (<= majorRadius[0])
    843                           |             . .  .__/\____
    844                           |             . .  | >      |
    845                           |             . [] | > \/   |
    846                           |             .    | > /\   |
    847                           |             .    |_>______|
    848                           |             .    . >
    849                           |             .    . >
    850                           |             .    bbox[2]+dis2From-majorRadius[0]
    851                           |             .      >
    852                           |             .______>_
    853                           |             |      > |
    854                           |         []  |   \/ > |
    855                           |             |   /\ > |
    856                           |             |______>_|
    857                           |             .    \_/
    858                           |             .     dis2To (<= majorRadius[0])
    859                           |             .      >
    860                           |             .      >
    861                           |             bbox[2]-dis2To-majorRadius[0]
    862                      */
    863                     dis0To = Math.abs(bbox[0] % majorStep[0]);
    864                     dis1To = Math.abs(bbox[1] % majorStep[1]);
    865                     dis2To = Math.abs(bbox[2] % majorStep[0]);
    866                     dis3To = Math.abs(bbox[3] % majorStep[1]);
    867                     dis0From = majorStep[0] - dis0To;
    868                     dis1From = majorStep[1] - dis1To;
    869                     dis2From = majorStep[0] - dis2To;
    870                     dis3From = majorStep[1] - dis3To;
    871
    872                     if (
    873                         !includeBoundaries && (
    874                             (x - minorRadius[0] - bbox[0] - majorRadius[0] + dis0From < eps && dis0From - majorRadius[0] < eps) ||
    875                             (x - minorRadius[0] - bbox[0] - majorRadius[0] - dis0To < eps && dis0To - majorRadius[0] < eps) ||
    876                             (-x - minorRadius[0] + bbox[2] - majorRadius[0] + dis2From < eps && dis2From - majorRadius[0] < eps) ||
    877                             (-x - minorRadius[0] + bbox[2] - majorRadius[0] - dis2To < eps && dis2To - majorRadius[0] < eps) ||
    878
    879                             (-y - minorRadius[1] + bbox[1] - majorRadius[1] + dis1From < eps && dis1From - majorRadius[1] < eps) ||
    880                             (-y - minorRadius[1] + bbox[1] - majorRadius[1] - dis1To < eps && dis1To - majorRadius[1] < eps) ||
    881                             (y - minorRadius[1] - bbox[3] - majorRadius[1] + dis3From < eps && dis3From - majorRadius[1] < eps) ||
    882                             (y - minorRadius[1] - bbox[3] - majorRadius[1] - dis3To < eps && dis3To - majorRadius[1] < eps) ||
    883
    884                             (-y - minorRadius[1] + bbox[1] < eps) ||
    885                             (x - minorRadius[0] - bbox[0] < eps) ||
    886                             (y - minorRadius[1] - bbox[3] < eps) ||
    887                             (-x - minorRadius[0] + bbox[2] < eps)
    888                         )
    889                     ) {
    890                         continue;
    891                     }
    892
    893                     dataArr = createDataArrayForFace(minorFace, minorGrid, x, y, minorRadius[0], minorRadius[1], bbox);
    894                     Type.concat(this.dataX, dataArr[0]);
    895                     Type.concat(this.dataY, dataArr[1]);
    896                 }
    897             }
    898         } else {
    899             m = minorGrid.evalVisProp('margin');
    900             for (y = startY; finite && y >= bbox[3]; y -= minorStep[1]) {
    901                 YdisTo0 = Mat.roundToStep(Math.abs(y), majorStep[1]);
    902                 YdisTo0 = Math.abs(YdisTo0 - Math.abs(y));
    903                 YdisFrom0 = majorStep[1] - YdisTo0;
    904
    905                 if (majorFace === 'line') {
    906                     // for majorFace 'line' do not draw minor grid elements on lines
    907                     if (
    908                         YdisTo0 - minorRadius[1] - majorRadius[1] < eps ||
    909                         YdisFrom0 - minorRadius[1] - majorRadius[1] < eps
    910                     ) {
    911                         continue;
    912                     }
    913
    914                 } else {
    915                     if ((
    916                         YdisTo0 - minorRadius[1] - majorRadius[1] < eps ||
    917                         YdisFrom0 - minorRadius[1] - majorRadius[1] < eps
    918                     )) {
    919                         // if major grid elements (on 0 or axes) are not existing, minor grid elements have to exist. Otherwise:
    920                         if ((
    921                             majorDrawZeroOrigin ||
    922                             majorRadius[1] - Math.abs(y) + minorRadius[1] < eps
    923                         ) && (
    924                                 majorDrawZeroX ||
    925                                 majorRadius[1] - Math.abs(y) + minorRadius[1] < eps
    926                             ) && (
    927                                 majorDrawZeroY ||
    928                                 majorRadius[1] + Math.abs(y) - minorRadius[1] < eps
    929                             )) {
    930                             continue;
    931                         }
    932                     }
    933                 }
    934                 if (!minorDrawZeroX && Math.abs(y) < eps) {
    935                     continue;
    936                 }
    937
    938                 dis0To = Math.abs(bbox[0] % majorStep[0]);
    939                 dis1To = Math.abs(bbox[1] % majorStep[1]);
    940                 dis2To = Math.abs(bbox[2] % majorStep[0]);
    941                 dis3To = Math.abs(bbox[3] % majorStep[1]);
    942                 dis0From = majorStep[0] - dis0To;
    943                 dis1From = majorStep[1] - dis1To;
    944                 dis2From = majorStep[0] - dis2To;
    945                 dis3From = majorStep[1] - dis3To;
    946
    947                 if (
    948                     !includeBoundaries && (
    949                         (-y - minorRadius[1] + bbox[1] - majorRadius[1] + dis1From < eps && dis1From - majorRadius[1] < eps) ||
    950                         (-y - minorRadius[1] + bbox[1] - majorRadius[1] - dis1To < eps && dis1To - majorRadius[1] < eps) ||
    951                         (y - minorRadius[1] - bbox[3] - majorRadius[1] + dis3From < eps && dis3From - majorRadius[1] < eps) ||
    952                         (y - minorRadius[1] - bbox[3] - majorRadius[1] - dis3To < eps && dis3To - majorRadius[1] < eps) ||
    953
    954                         (-y - minorRadius[1] + bbox[1] < eps) ||
    955                         (y - minorRadius[1] - bbox[3] < eps)
    956                     )
    957                 ) {
    958                     continue;
    959                 }
    960
    961                 dataArr = [
    962                     [bbox[0] - m / minorGrid.board.unitX, bbox[2] + m / minorGrid.board.unitX, NaN],
    963                     [y, y, NaN]
    964                 ];
    965                 Type.concat(this.dataX, dataArr[0]);
    966                 Type.concat(this.dataY, dataArr[1]);
    967             }
    968             for (x = startX; finite && x <= bbox[2]; x += minorStep[0]) {
    969                 XdisTo0 = Mat.roundToStep(Math.abs(x), majorStep[0]);
    970                 XdisTo0 = Math.abs(XdisTo0 - Math.abs(x));
    971                 XdisFrom0 = majorStep[0] - XdisTo0;
    972
    973                 if (majorFace === 'line') {
    974                     // for majorFace 'line' do not draw minor grid elements on lines
    975                     if (
    976                         XdisTo0 - minorRadius[0] - majorRadius[0] < eps ||
    977                         XdisFrom0 - minorRadius[0] - majorRadius[0] < eps
    978                     ) {
    979                         continue;
    980                     }
    981
    982                 } else {
    983                     if ((
    984                         XdisTo0 - minorRadius[0] - majorRadius[0] < eps ||
    985                         XdisFrom0 - minorRadius[0] - majorRadius[0] < eps
    986                     )) {
    987                         // if major grid elements (on 0 or axes) are not existing, minor grid elements have to exist. Otherwise:
    988                         if ((
    989                             majorDrawZeroOrigin ||
    990                             majorRadius[0] - Math.abs(x) + minorRadius[0] < eps
    991                         ) && (
    992                                 majorDrawZeroX ||
    993                                 majorRadius[0] + Math.abs(x) - minorRadius[0] < eps
    994                             ) && (
    995                                 majorDrawZeroY ||
    996                                 majorRadius[0] - Math.abs(x) + minorRadius[0] < eps
    997                             )) {
    998                             continue;
    999                         }
    1000                     }
    1001                 }
    1002                 if (!minorDrawZeroY && Math.abs(x) < eps) {
    1003                     continue;
    1004                 }
    1005
    1006                 dis0To = Math.abs(bbox[0] % majorStep[0]);
    1007                 dis1To = Math.abs(bbox[1] % majorStep[1]);
    1008                 dis2To = Math.abs(bbox[2] % majorStep[0]);
    1009                 dis3To = Math.abs(bbox[3] % majorStep[1]);
    1010                 dis0From = majorStep[0] - dis0To;
    1011                 dis1From = majorStep[1] - dis1To;
    1012                 dis2From = majorStep[0] - dis2To;
    1013                 dis3From = majorStep[1] - dis3To;
    1014
    1015                 if (
    1016                     !includeBoundaries && (
    1017                         (x - minorRadius[0] - bbox[0] - majorRadius[0] + dis0From < eps && dis0From - majorRadius[0] < eps) ||
    1018                         (x - minorRadius[0] - bbox[0] - majorRadius[0] - dis0To < eps && dis0To - majorRadius[0] < eps) ||
    1019                         (-x - minorRadius[0] + bbox[2] - majorRadius[0] + dis2From < eps && dis2From - majorRadius[0] < eps) ||
    1020                         (-x - minorRadius[0] + bbox[2] - majorRadius[0] - dis2To < eps && dis2To - majorRadius[0] < eps) ||
    1021
    1022                         (x - minorRadius[0] - bbox[0] < eps) ||
    1023                         (-x - minorRadius[0] + bbox[2] < eps)
    1024                     )
    1025                 ) {
    1026                     continue;
    1027                 }
    1028
    1029                 dataArr = [
    1030                     [x, x, NaN],
    1031                     [bbox[1] + m / minorGrid.board.unitY, bbox[3] - m / minorGrid.board.unitY, NaN]
    1032                 ];
    1033                 Type.concat(this.dataX, dataArr[0]);
    1034                 Type.concat(this.dataY, dataArr[1]);
    1035             }
    1036         }
    1037     };
    1038
    1039     board.grids.push(majorGrid);
    1040     board.grids.push(minorGrid);
    1041
    1042     minorGrid.dump = false;
    1043
    1044     majorGrid.getParents = minorGrid.getParents = function() {
    1045         return parentAxes.slice();
    1046     };
    1047
    1048     return majorGrid;
    1049 };
    1050
    1051 JXG.registerElement("grid", JXG.createGrid);
    1052