1 /*
  2     Copyright 2008-2024
  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 Creates a grid to support the user with element placement or to improve determination of position.
 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     return majorGrid;
1043 };
1044 
1045 JXG.registerElement("grid", JXG.createGrid);
1046