1 /*
  2     Copyright 2008-2023
  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 import JXG from "../jxg";
 31 import Mat from "../math/math";
 32 import Type from "../utils/type";
 33 import Const from "../base/constants";
 34 
 35 /**
 36  * @class Creates a grid to support the user with element placement or to improve determination of position.
 37  * @pseudo
 38  * @description A grid is a set of vertical and horizontal lines or other geometrical objects (faces)
 39  * to support the user with element placement or to improve determination of position.
 40  * This method takes up to two facultative parent elements. These are used to set distance between
 41  * grid elements in case of attribute <tt>majorStep</tt> or <tt>minorElements</tt> is set to 'auto'.
 42  * Then the major/minor grid element distance is set to the ticks distance of parent axes.
 43  * It is usually instantiated on the board's creation via the attribute <tt>grid</tt> set to true.
 44  * @constructor
 45  * @name Grid
 46  * @type JXG.Curve
 47  * @augments JXG.Curve
 48  * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
 49  * @param {JXG.Axis_JXG.Axis} a1,a2 Optional parent axis.
 50  *
 51  * @example
 52  * // standard grid
 53  * var g = board.create('grid', [], {
 54  *     drawZero: true,
 55  * });
 56  * </pre><div id="JXGc8dde3f5-22ef-4c43-9505-34b299b5b24d" class="jxgbox" style="width: 300px; height: 300px;"></div>
 57  * <script type="text/javascript">
 58  *  (function() {
 59  *      var board = JXG.JSXGraph.initBoard('JXGc8dde3f5-22ef-4c43-9505-34b299b5b24d',
 60  *          {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false});
 61  *      var g = board.create('grid', [], {
 62  *          drawZero: true,
 63  *      });
 64  *  })();
 65  * </script><pre>
 66  *
 67  * @example
 68  * // more fancy grid
 69  * var g = board.create('grid', [], {
 70  *     major: {
 71  *         face: 'plus',
 72  *         size: 10,
 73  *         strokeColor: '#080050',
 74  *         strokeOpacity: 1,
 75  *     },
 76  *     minor: {
 77  *         size: 3
 78  *     },
 79  *     drawZero: true,
 80  *     minorElements: 4,
 81  * });
 82  * </pre><div id="JXG02374171-b27c-4ccc-a14a-9f5bd1162623" class="jxgbox" style="width: 300px; height: 300px;"></div>
 83  * <script type="text/javascript">
 84  *     (function() {
 85  *         var board = JXG.JSXGraph.initBoard('JXG02374171-b27c-4ccc-a14a-9f5bd1162623',
 86  *             {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false});
 87  *         var g = board.create('grid', [], {
 88  *             major: {
 89  *                 face: 'plus',
 90  *                 size: 10,
 91  *                 strokeColor: '#080050',
 92  *                 strokeOpacity: 1,
 93  *             },
 94  *             minor: {
 95  *                 size: 3
 96  *             },
 97  *             drawZero: true,
 98  *             minorElements: 4,
 99  *         });
100  *     })();
101  * </script><pre>
102  *
103  * @example
104  * // extreme fancy grid
105  * var grid = board.create('grid', [], {
106  *     major: {
107  *         face: 'regularPolygon',
108  *         size: 10,
109  *         strokeColor: 'blue',
110  *         fillColor: 'orange',
111  *         strokeOpacity: 1,
112  *         drawZero: true,
113  *     },
114  *     minor: {
115  *         face: 'diamond',
116  *         size: 3,
117  *         strokeColor: 'green',
118  *         fillColor: 'grey',
119  *         drawZero: true,
120  *     },
121  *     minorElements: 1,
122  *     includeBoundaries: false,
123  * });
124  * </pre><div id="JXG00f3d068-093c-4c1d-a1ab-96c9ee73c173" class="jxgbox" style="width: 300px; height: 300px;"></div>
125  * <script type="text/javascript">
126  *     (function() {
127  *         var board = JXG.JSXGraph.initBoard('JXG00f3d068-093c-4c1d-a1ab-96c9ee73c173',
128  *             {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false});
129  *         var grid = board.create('grid', [], {
130  *             major: {
131  *                 face: 'regularPolygon',
132  *                 size: 10,
133  *                 strokeColor: 'blue',
134  *                 fillColor: 'orange',
135  *                 strokeOpacity: 1,
136  *                 drawZero: true,
137  *             },
138  *             minor: {
139  *                 face: 'diamond',
140  *                 size: 3,
141  *                 strokeColor: 'green',
142  *                 fillColor: 'grey',
143  *                 drawZero: true,
144  *             },
145  *             minorElements: 1,
146  *             includeBoundaries: false,
147  *         });
148  *     })();
149  * </script><pre>
150  *
151  * @example
152  * // grid with parent axes
153  * var axis1 = board.create('axis', [[-1, -2.5], [1, -2.5]], {
154  *     ticks: {
155  *         strokeColor: 'green',
156  *         strokeWidth: 2,
157  *         minorticks: 2,
158  *         majorHeight: 10,
159  *         drawZero: true
160  *     }
161  * });
162  * var axis2 = board.create('axis', [[3, 0], [3, 2]], {
163  *     ticks: {
164  *         strokeColor: 'red',
165  *         strokeWidth: 2,
166  *         minorticks: 3,
167  *         majorHeight: 10,
168  *         drawZero: true
169  *     }
170  * });
171  * var grid = board.create('grid', [axis1, axis2], {
172  *     major: {
173  *         face: 'line',
174  *         drawZero: true
175  *     },
176  *     minor: {
177  *         face: 'point',
178  *         size: 3
179  *     },
180  *     minorElements: 'auto',
181  *     includeBoundaries: false,
182  * });
183  * </pre><div id="JXG0568e385-248c-43a9-87ed-07aceb8cc3ab" class="jxgbox" style="width: 300px; height: 300px;"></div>
184  * <script type="text/javascript">
185  *     (function() {
186  *         var board = JXG.JSXGraph.initBoard('JXG0568e385-248c-43a9-87ed-07aceb8cc3ab',
187  *             {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false});
188  *         var axis1 = board.create('axis', [[-1, -2.5], [1, -2.5]], {
189  *             ticks: {
190  *                 strokeColor: 'green',
191  *                 strokeWidth: 2,
192  *                 minorticks: 2,
193  *                 majorHeight: 10,
194  *                 drawZero: true
195  *             }
196  *         });
197  *         var axis2 = board.create('axis', [[3, 0], [3, 2]], {
198  *             ticks: {
199  *                 strokeColor: 'red',
200  *                 strokeWidth: 2,
201  *                 minorticks: 3,
202  *                 majorHeight: 10,
203  *                 drawZero: true
204  *             }
205  *         });
206  *         var grid = board.create('grid', [axis1, axis2], {
207  *             major: {
208  *                 face: 'line',
209  *                 drawZero: true
210  *             },
211  *             minor: {
212  *                 face: 'point',
213  *                 size: 3
214  *             },
215  *             minorElements: 'auto',
216  *             includeBoundaries: false,
217  *         });
218  *     }());
219  * </script><pre>
220  */
221 JXG.createGrid = function (board, parents, attributes) {
222     const eps = Mat.eps,       // to avoid rounding errors
223         maxLines = 5000;    // maximum number of vertical or horizontal grid elements (abort criterion for performance reasons)
224 
225     var majorGrid,      // main object which will be returned as grid
226         minorGrid,      // sub-object
227         parentAxes,     // {Array} array of user defined axes (allowed length 0, 1 or 2)
228 
229         attrGrid,       // attributes for grid
230         attrMajor,      // attributes for major grid
231         attrMinor,      // attributes for minor grid
232 
233         majorStep,      // {[Number]} distance (in usrCoords) in x- and y-direction between center of two major grid elements
234         majorSize = [],
235         majorRadius = [], // half of the size of major grid element
236 
237         createDataArrayForFace;  // {Function}
238 
239     parentAxes = parents;
240     if (
241         parentAxes.length > 2 ||
242         (parentAxes.length >= 1 && parentAxes[0].elType !== 'axis') ||
243         (parentAxes.length >= 2 && parentAxes[1].elType !== 'axis')
244     ) {
245         throw new Error(
246             "JSXGraph: Can't create 'grid' with parent type '" +
247             parents[0].elType +
248             "'. Possible parent types: [axis,axis]"
249         );
250     }
251     if (!Type.exists(parentAxes[0]) && Type.exists(board.defaultAxes)) {
252         parentAxes[0] = board.defaultAxes.x;
253     }
254     if (!Type.exists(parentAxes[1]) && Type.exists(board.defaultAxes)) {
255         parentAxes[1] = board.defaultAxes.y;
256     }
257 
258     /**
259      * Creates for each face the right data array for updateDataArray function.
260      * This functions also adapts visProps according to face.
261 
262      * @param {String} face Chosen face to be drawn
263      * @param {Object} grid Curve/grid to be drawn
264      * @param {Number} x x-coordinate of target position
265      * @param {Number} y y-coordinate of target position
266      * @param {Number} radiusX Half of width in x-direction of face to be drawn
267      * @param {Number} radiusY Half of width in y-direction of face to be drawn
268      * @param {Array} bbox boundingBox
269      *
270      * @returns {Array} data array of length 2 (x- and y- coordinated for curve)
271      * @private
272      * @ignore
273      */
274     createDataArrayForFace = function (face, grid, x, y, radiusX, radiusY, bbox) {
275         var t, q, m, n, array, rx2, ry2;
276 
277         switch (face.toLowerCase()) {
278 
279             // filled point
280             case '.':
281             case 'point':
282                 grid.visProp.linecap = 'round';
283                 return [
284                     [x, x, NaN],
285                     [y, y, NaN]
286                 ];
287 
288             // bezierCircle
289             case 'o':
290             case 'circle':
291                 grid.visProp.linecap = 'square';
292                 grid.bezierDegree = 3;
293                 q = 4 * Math.tan(Math.PI / 8) / 3;
294                 return [
295                     [
296                         x + radiusX, x + radiusX, x + q * radiusX, x,
297                         x - q * radiusX, x - radiusX, x - radiusX, x - radiusX,
298                         x - q * radiusX, x, x + q * radiusX, x + radiusX,
299                         x + radiusX, NaN
300                     ], [
301                         y, y + q * radiusY, y + radiusY, y + radiusY,
302                         y + radiusY, y + q * radiusY, y, y - q * radiusY,
303                         y - radiusY, y - radiusY, y - radiusY, y - q * radiusY,
304                         y, NaN
305                     ]
306                 ];
307 
308             // polygon
309             case 'regpol':
310             case 'regularpolygon':
311                 grid.visProp.linecap = 'round';
312                 n = Type.evaluate(grid.visProp.polygonvertices);
313                 array = [[], []];
314                 // approximation of circle with variable n
315                 for (t = 0; t <= 2 * Math.PI; t += (2 * Math.PI) / n) {
316                     array[0].push(x - radiusX * Math.sin(t));
317                     array[1].push(y - radiusY * Math.cos(t));
318                 }
319                 array[0].push(NaN);
320                 array[1].push(NaN);
321                 return array;
322 
323             // square
324             case '[]':
325             case 'square':
326                 grid.visProp.linecap = 'square';
327                 return [
328                     [x - radiusX, x + radiusX, x + radiusX, x - radiusX, x - radiusX, NaN],
329                     [y + radiusY, y + radiusY, y - radiusY, y - radiusY, y + radiusY, NaN]
330                 ];
331 
332             // diamond
333             case '<>':
334             case 'diamond':
335                 grid.visProp.linecap = 'square';
336                 return [
337                     [x, x + radiusX, x, x - radiusX, x, NaN],
338                     [y + radiusY, y, y - radiusY, y, y + radiusY, NaN]
339                 ];
340 
341             // diamond2
342             case '<<>>':
343             case 'diamond2':
344                 grid.visProp.linecap = 'square';
345                 rx2 = radiusX * Math.sqrt(2);
346                 ry2 = radiusY * Math.sqrt(2);
347                 return [
348                     [x, x + rx2, x, x - rx2, x, NaN],
349                     [y + ry2, y, y - ry2, y, y + ry2, NaN]
350                 ];
351 
352             case 'x':
353             case 'cross':
354                 return [
355                     [x - radiusX, x + radiusX, NaN, x - radiusX, x + radiusX, NaN],
356                     [y + radiusY, y - radiusY, NaN, y - radiusY, y + radiusY, NaN]
357                 ];
358 
359             case '+':
360             case 'plus':
361                 return [
362                     [x - radiusX, x + radiusX, NaN, x, x, NaN],
363                     [y, y, NaN, y - radiusY, y + radiusY, NaN]
364                 ];
365 
366             case '-':
367             case 'minus':
368                 return [
369                     [x - radiusX, x + radiusX, NaN],
370                     [y, y, NaN]
371                 ];
372 
373             case '|':
374             case 'divide':
375                 return [
376                     [x, x, NaN],
377                     [y - radiusY, y + radiusY, NaN]
378                 ];
379 
380             case '^':
381             case 'a':
382             case 'A':
383             case 'triangleup':
384                 return [
385                     [x - radiusX, x, x + radiusX, NaN],
386                     [y - radiusY, y, y - radiusY, NaN]
387                 ];
388 
389             case 'v':
390             case 'triangledown':
391                 return [
392                     [x - radiusX, x, x + radiusX, NaN],
393                     [y + radiusY, y, y + radiusY, NaN]
394                 ];
395 
396             case '<':
397             case 'triangleleft':
398                 return [
399                     [x + radiusX, x, x + radiusX, NaN],
400                     [y + radiusY, y, y - radiusY, NaN]
401                 ];
402 
403             case '>':
404             case 'triangleright':
405                 return [
406                     [x - radiusX, x, x - radiusX, NaN],
407                     [y + radiusY, y, y - radiusY, NaN]
408                 ];
409 
410             case 'line':
411                 m = Type.evaluate(grid.visProp.margin);
412                 return [
413                     // [x, x, NaN, bbox[0] + (4 / grid.board.unitX), bbox[2] - (4 / grid.board.unitX), NaN],
414                     [x, x, NaN, bbox[0] - m / grid.board.unitX, bbox[2] + m / grid.board.unitX, NaN],
415                     [bbox[1] + m / grid.board.unitY, bbox[3] - m / grid.board.unitY, NaN, y, y, NaN]
416                 ];
417 
418             default:
419                 return [[], []];
420         }
421     };
422 
423     // Themes
424     attrGrid = Type.copyAttributes(attributes, board.options, 'grid');
425     Type.mergeAttr(board.options.grid, attrGrid.themes[attrGrid.theme], false); // POI: I think there should not be `board.options.grid`
426     attrGrid = Type.copyAttributes(attributes, board.options, 'grid');
427 
428     // Create majorGrid
429     attrMajor = Type.copyAttributes(attributes, board.options, 'grid', 'major');
430     Type.mergeAttr(attrMajor, attrGrid, true);
431     majorGrid = board.create('curve', [[null], [null]], attrMajor);
432     majorGrid.elType = 'grid';
433     majorGrid.type = Const.OBJECT_TYPE_GRID;
434 
435     // Create minorGrid
436     attrMinor = Type.copyAttributes(attributes, board.options, 'grid', 'minor');
437     Type.mergeAttr(attrMinor, attrGrid, true);
438     if (attrMinor.id === attrMajor.id) {
439         attrMinor.id = attrMajor.id + '_minor';
440     }
441     if (attrMinor.name === attrMajor.name) {
442         attrMinor.name = attrMajor.name + '_minor';
443     }
444     minorGrid = board.create('curve', [[null], [null]], attrMinor);
445     minorGrid.elType = 'grid';
446     minorGrid.type = Const.OBJECT_TYPE_GRID;
447 
448     majorGrid.minorGrid = minorGrid;
449     minorGrid.majorGrid = majorGrid;
450 
451     majorGrid.hasPoint = function () { return false; };
452     minorGrid.hasPoint = function () { return false; };
453 
454     majorGrid.inherits.push(minorGrid);
455 
456     majorGrid.updateDataArray = function () {
457         var bbox = this.board.getBoundingBox(),
458             startX, startY,
459             x, y,
460             dataArr,
461             finite,
462 
463             gridX = Type.evaluate(this.visProp.gridx), // for backwards compatibility
464             gridY = Type.evaluate(this.visProp.gridy), // for backwards compatibility
465             face = Type.evaluate(this.visProp.face),
466             drawZero = Type.evaluate(this.visProp.drawzero),
467             drawZeroOrigin = drawZero === true || (Type.isObject(drawZero) && Type.evaluate(drawZero.origin) === true),
468             drawZeroX = drawZero === true || (Type.isObject(drawZero) && Type.evaluate(drawZero.x) === true),
469             drawZeroY = drawZero === true || (Type.isObject(drawZero) && Type.evaluate(drawZero.y) === true),
470 
471             includeBoundaries = Type.evaluate(this.visProp.includeboundaries),
472             forceSquare = Type.evaluate(this.visProp.forcesquare);
473 
474         this.dataX = [];
475         this.dataY = [];
476 
477         // set global majorStep
478         majorStep = Type.evaluate(this.visProp.majorstep);
479         if (!Type.isArray(majorStep)) {
480             majorStep = [majorStep, majorStep];
481         }
482         if (majorStep.length < 2) {
483             majorStep = [majorStep[0], majorStep[0]];
484         }
485         if (Type.exists(gridX)) {
486             JXG.deprecated("gridX", "majorStep");
487             majorStep[0] = gridX;
488         }
489         if (Type.exists(gridY)) {
490             JXG.deprecated("gridY", "majorStep");
491             majorStep[1] = gridY;
492         }
493 
494         if (majorStep[0] === 'auto') {
495             majorStep[0] = 1; // parentAxes[0] may not be defined
496             if (Type.exists(parentAxes[0])) {
497                 majorStep[0] = parentAxes[0].ticks[0].getDistanceMajorTicks();
498             }
499         } else {
500             // This allows the value to hate unit px, abs, % or fr.
501             majorStep[0] = Type.parseNumber(majorStep[0], Math.abs(bbox[1] - bbox[3]), 1 / this.board.unitX);
502         }
503         if (majorStep[1] === 'auto') {
504             majorStep[1] = 1; // parentAxes[1] may not be defined
505             if (Type.exists(parentAxes[1])) {
506                 majorStep[1] = parentAxes[1].ticks[0].getDistanceMajorTicks();
507             }
508         } else {
509             // This allows the value to hate unit px, abs, % or fr.
510             majorStep[1] = Type.parseNumber(majorStep[1], Math.abs(bbox[0] - bbox[2]), 1 / this.board.unitY);
511         }
512 
513         if (forceSquare === 'min' || forceSquare === true) {
514             if (majorStep[0] * this.board.unitX <= majorStep[1] * this.board.unitY) { // compare px-values
515                 majorStep[1] = majorStep[0] / this.board.unitY * this.board.unitX;
516             } else {
517                 majorStep[0] = majorStep[1] / this.board.unitX * this.board.unitY;
518             }
519         } else if (forceSquare === 'max') {
520             if (majorStep[0] * this.board.unitX <= majorStep[1] * this.board.unitY) { // compare px-values
521                 majorStep[0] = majorStep[1] / this.board.unitX * this.board.unitY;
522             } else {
523                 majorStep[1] = majorStep[0] / this.board.unitY * this.board.unitX;
524             }
525         }
526 
527         // set global majorSize
528         majorSize = Type.evaluate(this.visProp.size);
529         if (!Type.isArray(majorSize)) {
530             majorSize = [majorSize, majorSize];
531         }
532         if (majorSize.length < 2) {
533             majorSize = [majorSize[0], majorSize[0]];
534         }
535 
536         if (Type.isNumber(majorSize[0], true) || majorSize[0].indexOf('abs') > -1) {
537             majorSize[0] = ("" + majorSize[0]).replace(/\s+abs\s+/, '') + "px"; // interpret number as pixels
538         }
539         if (Type.isNumber(majorSize[1], true) || majorSize[1].indexOf('abs') > -1) {
540             majorSize[1] = ("" + majorSize[1]).replace(/\s+abs\s+/, '') + "px"; // interpret number as pixels
541         }
542 
543         majorSize[0] = Type.parseNumber(majorSize[0], majorStep[0], 1 / this.board.unitX);
544         majorRadius[0] = majorSize[0] / 2;
545         majorSize[1] = Type.parseNumber(majorSize[1], majorStep[1], 1 / this.board.unitY);
546         majorRadius[1] = majorSize[1] / 2;
547 
548         // calculate start position of curve
549         startX = Mat.roundToStep(bbox[0], majorStep[0]);
550         startY = Mat.roundToStep(bbox[1], majorStep[1]);
551 
552         // check if number of grid elements side by side is not too large
553         finite = isFinite(startX) && isFinite(startY) &&
554             isFinite(bbox[2]) && isFinite(bbox[3]) &&
555             Math.abs(bbox[2]) < Math.abs(majorStep[0] * maxLines) &&
556             Math.abs(bbox[3]) < Math.abs(majorStep[1] * maxLines);
557 
558         // POI finite = false means that no grid is drawn. Should we change this?
559 
560         // draw grid elements
561         for (y = startY; finite && y >= bbox[3]; y -= majorStep[1]) {
562             for (x = startX; finite && x <= bbox[2]; x += majorStep[0]) {
563 
564                 if (
565                     (!drawZeroOrigin && Math.abs(y) < eps && Math.abs(x) < eps) ||
566                     (!drawZeroX && Math.abs(y) < eps && Math.abs(x) >= eps) ||
567                     (!drawZeroY && Math.abs(x) < eps && Math.abs(y) >= eps) ||
568                     (!includeBoundaries && (
569                         x <= bbox[0] + majorRadius[0] ||
570                         x >= bbox[2] - majorRadius[0] ||
571                         y <= bbox[3] + majorRadius[1] ||
572                         y >= bbox[1] - majorRadius[1]
573                     ))
574                 ) {
575                     continue;
576                 }
577 
578                 dataArr = createDataArrayForFace(face, majorGrid, x, y, majorRadius[0], majorRadius[1], bbox);
579                 this.dataX = this.dataX.concat(dataArr[0]);
580                 this.dataY = this.dataY.concat(dataArr[1]);
581             }
582         }
583     };
584 
585     minorGrid.updateDataArray = function () {
586         var bbox = this.board.getBoundingBox(),
587             startX, startY,
588             x, y,
589             dataArr,
590             finite,
591 
592             minorStep = [],
593             minorRadius = [],
594             XdisTo0, XdisFrom0, YdisTo0, YdisFrom0, // {Number} absolute distances of minor grid elements center to next major grid element center
595             dis0To, dis1To, dis2To, dis3To,         // {Number} absolute distances of borders of the boundingBox to the next major grid element.
596             dis0From, dis1From, dis2From, dis3From,
597 
598             minorElements = Type.evaluate(this.visProp.minorelements),
599             minorSize = Type.evaluate(this.visProp.size),
600             minorFace = Type.evaluate(this.visProp.face),
601             minorDrawZero = Type.evaluate(this.visProp.drawzero),
602             minorDrawZeroX = minorDrawZero === true || (Type.isObject(minorDrawZero) && Type.evaluate(minorDrawZero.x) === true),
603             minorDrawZeroY = minorDrawZero === true || (Type.isObject(minorDrawZero) && Type.evaluate(minorDrawZero.y) === true),
604 
605             majorFace = Type.evaluate(this.majorGrid.visProp.face),
606             majorDrawZero = Type.evaluate(this.majorGrid.visProp.drawzero),
607             majorDrawZeroOrigin = majorDrawZero === true || (Type.isObject(majorDrawZero) && Type.evaluate(majorDrawZero.origin) === true),
608             majorDrawZeroX = majorDrawZero === true || (Type.isObject(majorDrawZero) && Type.evaluate(majorDrawZero.x) === true),
609             majorDrawZeroY = majorDrawZero === true || (Type.isObject(majorDrawZero) && Type.evaluate(majorDrawZero.y) === true),
610 
611             includeBoundaries = Type.evaluate(this.visProp.includeboundaries);
612 
613         this.dataX = [];
614         this.dataY = [];
615 
616         // set minorStep
617         // minorElements can be 'auto' or a number (also a number like '20')
618         if (!Type.isArray(minorElements)) {
619             minorElements = [minorElements, minorElements];
620         }
621         if (minorElements.length < 2) {
622             minorElements = [minorElements[0], minorElements[0]];
623         }
624 
625         if (Type.isNumber(minorElements[0], true)) {
626             minorElements[0] = parseFloat(minorElements[0]);
627 
628         } else { // minorElements[0]  === 'auto'
629             minorElements[0] = 0; // parentAxes[0] may not be defined
630             if (Type.exists(parentAxes[0])) {
631                 minorElements[0] = Type.evaluate(parentAxes[0].getAttribute('ticks').minorticks);
632             }
633         }
634         minorStep[0] = majorStep[0] / (minorElements[0] + 1);
635 
636         if (Type.isNumber(minorElements[1], true)) {
637             minorElements[1] = parseFloat(minorElements[1]);
638 
639         } else { // minorElements[1] === 'auto'
640             minorElements[1] = 0; // parentAxes[1] may not be defined
641             if (Type.exists(parentAxes[1])) {
642                 minorElements[1] = Type.evaluate(parentAxes[1].getAttribute('ticks').minorticks);
643             }
644         }
645         minorStep[1] = majorStep[1] / (minorElements[1] + 1);
646 
647         // set global minorSize
648         if (!Type.isArray(minorSize)) {
649             minorSize = [minorSize, minorSize];
650         }
651         if (minorSize.length < 2) {
652             minorSize = [minorSize[0], minorSize[0]];
653         }
654 
655         // minorRadius = [
656         //     Type.parseNumber(minorSize[0], minorStep[0] * 0.5, 1 / this.board.unitX),
657         //     Type.parseNumber(minorSize[0], minorStep[0] * 0.5, 1 / this.board.unitY)
658         // ];
659 
660         minorSize[0] = Type.parseNumber(minorSize[0], minorStep[0], 1 / this.board.unitX);
661         minorSize[1] = Type.parseNumber(minorSize[1], minorStep[1], 1 / this.board.unitY);
662         minorRadius[0] = minorSize[0] * 0.5;
663         minorRadius[1] = minorSize[1] * 0.5;
664 
665         // calculate start position of curve
666         startX = Mat.roundToStep(bbox[0], minorStep[0]);
667         startY = Mat.roundToStep(bbox[1], minorStep[1]);
668 
669         // check if number of grid elements side by side is not too large
670         finite = isFinite(startX) && isFinite(startY) &&
671             isFinite(bbox[2]) && isFinite(bbox[3]) &&
672             Math.abs(bbox[2]) <= Math.abs(minorStep[0] * maxLines) &&
673             Math.abs(bbox[3]) < Math.abs(minorStep[1] * maxLines);
674 
675         // POI finite = false means that no grid is drawn. Should we change this?
676 
677         // draw grid elements
678         for (y = startY; finite && y >= bbox[3]; y -= minorStep[1]) {
679             for (x = startX; finite && x <= bbox[2]; x += minorStep[0]) {
680 
681                 /* explanation:
682                      |<___XdisTo0___><___________XdisFrom0___________>
683                      |                .                .               .
684                  ____|____            .                .           _________
685                 |    |    |         ____              ____        |         |
686                 |    |    |        |    |            |    |       |         |
687                 |    |    |        |____|            |____|       |         |
688                 |____|____|           | |              .          |_________|
689                      |    |           . \              .              .
690                      |  \             . minorRadius[0]   .              .
691                      |   majorRadius[0] .                .              .
692                      |                .                .              .
693                      |<----------->   .                .              .
694                      |    \           .                .              .
695                      |     XdisTo0 - minorRadius[0] <= majorRadius[0] ? -> exclude
696                      |                .                .              .
697                      |                .  <--------------------------->
698                      |                             \
699                      |                              XdisFrom0 - minorRadius[0] <= majorRadius[0] ? -> exclude
700                      |
701                -——---|————————-————---|----------------|---------------|-------->
702                      |
703                      |<______________________majorStep[0]_____________________>
704                      |
705                      |<__minorStep[0]____><__minorStep[0]_____><__minorStep[0]_____>
706                      |
707                      |
708                 */
709                 XdisTo0 = Mat.roundToStep(Math.abs(x), majorStep[0]);
710                 XdisTo0 = Math.abs(XdisTo0 - Math.abs(x));
711                 XdisFrom0 = majorStep[0] - XdisTo0;
712 
713                 YdisTo0 = Mat.roundToStep(Math.abs(y), majorStep[1]);
714                 YdisTo0 = Math.abs(YdisTo0 - Math.abs(y));
715                 YdisFrom0 = majorStep[1] - YdisTo0;
716 
717                 if (majorFace === 'line') {
718                     // for majorFace 'line' do not draw minor grid elements on lines
719                     if (
720                         XdisTo0 - minorRadius[0] - majorRadius[0] < eps ||
721                         XdisFrom0 - minorRadius[0] - majorRadius[0] < eps ||
722                         YdisTo0 - minorRadius[1] - majorRadius[1] < eps ||
723                         YdisFrom0 - minorRadius[1] - majorRadius[1] < eps
724                     ) {
725                         continue;
726                     }
727 
728                 } else {
729                     if ((
730                         XdisTo0 - minorRadius[0] - majorRadius[0] < eps ||
731                         XdisFrom0 - minorRadius[0] - majorRadius[0] < eps
732                     ) && (
733                         YdisTo0 - minorRadius[1] - majorRadius[1] < eps ||
734                         YdisFrom0 - minorRadius[1] - majorRadius[1] < eps
735                     )) {
736                         // if major grid elements (on 0 or axes) are not existing, minor grid elements have to exist. Otherwise:
737                         if ((
738                             majorDrawZeroOrigin ||
739                             majorRadius[1] - Math.abs(y) + minorRadius[1] < eps ||
740                             majorRadius[0] - Math.abs(x) + minorRadius[0] < eps
741                         ) && (
742                             majorDrawZeroX ||
743                             majorRadius[1] - Math.abs(y) + minorRadius[1] < eps ||
744                             majorRadius[0] + Math.abs(x) - minorRadius[0] < eps
745                         ) && (
746                             majorDrawZeroY ||
747                             majorRadius[0] - Math.abs(x) + minorRadius[0] < eps ||
748                             majorRadius[1] + Math.abs(y) - minorRadius[1] < eps
749                         )) {
750                             continue;
751                         }
752                     }
753                 }
754                 if (
755                     (!minorDrawZeroY && Math.abs(x) < eps) ||
756                     (!minorDrawZeroX && Math.abs(y) < eps)
757                 ) {
758                     continue;
759                 }
760 
761                 /* explanation of condition below:
762 
763                       |         __dis2To___> _dis2From_      // dis2To bzw. dis2From >= majorRadius[0]
764                       |      __/_          \/         _\__
765                       |     |    |  []     >         |    |
766                       |     |____|         >         |____|
767                       |                    >
768                       |                    >
769                       |    x-minorSize[0]  > bbox[2]
770                       0               .    >/
771                    -——|————————-————.-.——.—>
772                       |             . .  . >
773                       |             . .  . >
774                       |             . .  . > dis2From (<= majorRadius[0])
775                       |             . .  .__/\____
776                       |             . .  | >      |
777                       |             . [] | > \/   |
778                       |             .    | > /\   |
779                       |             .    |_>______|
780                       |             .    . >
781                       |             .    . >
782                       |             .    bbox[2]+dis2From-majorRadius[0]
783                       |             .      >
784                       |             .______>_
785                       |             |      > |
786                       |         []  |   \/ > |
787                       |             |   /\ > |
788                       |             |______>_|
789                       |             .    \_/
790                       |             .     dis2To (<= majorRadius[0])
791                       |             .      >
792                       |             .      >
793                       |             bbox[2]-dis2To-majorRadius[0]
794                  */
795                 dis0To = Math.abs(bbox[0] % majorStep[0]);
796                 dis1To = Math.abs(bbox[1] % majorStep[1]);
797                 dis2To = Math.abs(bbox[2] % majorStep[0]);
798                 dis3To = Math.abs(bbox[3] % majorStep[1]);
799                 dis0From = majorStep[0] - dis0To;
800                 dis1From = majorStep[1] - dis1To;
801                 dis2From = majorStep[0] - dis2To;
802                 dis3From = majorStep[1] - dis3To;
803 
804                 if (
805                     !includeBoundaries && (
806                         (x - minorRadius[0] - bbox[0] - majorRadius[0] + dis0From < eps && dis0From - majorRadius[0] < eps) ||
807                         (x - minorRadius[0] - bbox[0] - majorRadius[0] - dis0To < eps && dis0To - majorRadius[0] < eps) ||
808                         (-x - minorRadius[0] + bbox[2] - majorRadius[0] + dis2From < eps && dis2From - majorRadius[0] < eps) ||
809                         (-x - minorRadius[0] + bbox[2] - majorRadius[0] - dis2To < eps && dis2To - majorRadius[0] < eps) ||
810 
811                         (-y - minorRadius[1] + bbox[1] - majorRadius[1] + dis1From < eps && dis1From - majorRadius[1] < eps) ||
812                         (-y - minorRadius[1] + bbox[1] - majorRadius[1] - dis1To < eps && dis1To - majorRadius[1] < eps) ||
813                         (y - minorRadius[1] - bbox[3] - majorRadius[1] + dis3From < eps && dis3From - majorRadius[1] < eps) ||
814                         (y - minorRadius[1] - bbox[3] - majorRadius[1] - dis3To < eps && dis3To - majorRadius[1] < eps) ||
815 
816                         (-y - minorRadius[1] + bbox[1] < eps) ||
817                         (x - minorRadius[0] - bbox[0] < eps) ||
818                         (y - minorRadius[1] - bbox[3] < eps) ||
819                         (-x - minorRadius[0] + bbox[2] < eps)
820                     )
821                 ) {
822                     continue;
823                 }
824 
825                 dataArr = createDataArrayForFace(minorFace, minorGrid, x, y, minorRadius[0], minorRadius[1], bbox);
826                 this.dataX = this.dataX.concat(dataArr[0]);
827                 this.dataY = this.dataY.concat(dataArr[1]);
828             }
829         }
830     };
831 
832     board.grids.push(majorGrid);
833     board.grids.push(minorGrid);
834 
835     return majorGrid;
836 };
837 
838 JXG.registerElement("grid", JXG.createGrid);
839