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