1 /* 2 Copyright 2008-2024 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Bianca Valentin, 7 Alfred Wassermann, 8 Peter Wilfahrt 9 10 This file is part of JSXGraph. 11 12 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 13 14 You can redistribute it and/or modify it under the terms of the 15 16 * GNU Lesser General Public License as published by 17 the Free Software Foundation, either version 3 of the License, or 18 (at your option) any later version 19 OR 20 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 21 22 JSXGraph is distributed in the hope that it will be useful, 23 but WITHOUT ANY WARRANTY; without even the implied warranty of 24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 GNU Lesser General Public License for more details. 26 27 You should have received a copy of the GNU Lesser General Public License and 28 the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/> 29 and <https://opensource.org/licenses/MIT/>. 30 */ 31 32 /*global JXG: true, define: true, console: true, window: true*/ 33 /*jslint nomen: true, plusplus: true*/ 34 35 /** 36 * @fileoverview The geometry object Point is defined in this file. Point stores all 37 * style and functional properties that are required to draw and move a point on 38 * a board. 39 */ 40 41 import JXG from "../jxg.js"; 42 import Options from "../options.js"; 43 import Mat from "../math/math.js"; 44 import Geometry from "../math/geometry.js"; 45 import Const from "./constants.js"; 46 import GeometryElement from "./element.js"; 47 import Type from "../utils/type.js"; 48 import CoordsElement from "./coordselement.js"; 49 50 /** 51 * A point is the basic geometric element. Based on points lines and circles can be constructed which can be intersected 52 * which in turn are points again which can be used to construct new lines, circles, polygons, etc. This class holds methods for 53 * all kind of points like free points, gliders, and intersection points. 54 * @class Creates a new point object. Do not use this constructor to create a point. Use {@link JXG.Board#create} with 55 * type {@link Point}, {@link Glider}, or {@link Intersection} instead. 56 * @augments JXG.GeometryElement 57 * @augments JXG.CoordsElement 58 * @param {string|JXG.Board} board The board the new point is drawn on. 59 * @param {Array} coordinates An array with the user coordinates of the point. 60 * @param {Object} attributes An object containing visual properties like in {@link JXG.Options#point} and 61 * {@link JXG.Options#elements}, and optional a name and an id. 62 * @see JXG.Board#generateName 63 */ 64 JXG.Point = function (board, coordinates, attributes) { 65 this.constructor(board, attributes, Const.OBJECT_TYPE_POINT, Const.OBJECT_CLASS_POINT); 66 this.element = this.board.select(attributes.anchor); 67 this.coordsConstructor(coordinates); 68 69 this.elType = "point"; 70 71 /* Register point at board. */ 72 this.id = this.board.setId(this, "P"); 73 this.board.renderer.drawPoint(this); 74 this.board.finalizeAdding(this); 75 76 this.createGradient(); 77 this.createLabel(); 78 }; 79 80 /** 81 * Inherits here from {@link JXG.GeometryElement}. 82 */ 83 JXG.Point.prototype = new GeometryElement(); 84 Type.copyPrototypeMethods(JXG.Point, CoordsElement, "coordsConstructor"); 85 86 JXG.extend( 87 JXG.Point.prototype, 88 /** @lends JXG.Point.prototype */ { 89 /** 90 * Checks whether (x,y) is near the point. 91 * @param {Number} x Coordinate in x direction, screen coordinates. 92 * @param {Number} y Coordinate in y direction, screen coordinates. 93 * @returns {Boolean} True if (x,y) is near the point, False otherwise. 94 * @private 95 */ 96 hasPoint: function (x, y) { 97 var coordsScr = this.coords.scrCoords, 98 r, 99 prec, 100 type, 101 unit = this.evalVisProp('sizeunit'); 102 103 if (Type.isObject(this.evalVisProp('precision'))) { 104 type = this.board._inputDevice; 105 prec = this.evalVisProp('precision.' + type); 106 } else { 107 // 'inherit' 108 prec = this.board.options.precision.hasPoint; 109 } 110 r = parseFloat(this.evalVisProp('size')); 111 if (unit === "user") { 112 r *= Math.sqrt(Math.abs(this.board.unitX * this.board.unitY)); 113 } 114 115 r += parseFloat(this.evalVisProp('strokewidth')) * 0.5; 116 if (r < prec) { 117 r = prec; 118 } 119 120 return Math.abs(coordsScr[1] - x) < r + 2 && Math.abs(coordsScr[2] - y) < r + 2; 121 }, 122 123 /** 124 * Updates the position of the point. 125 */ 126 update: function (fromParent) { 127 if (!this.needsUpdate) { 128 return this; 129 } 130 131 this.updateCoords(fromParent); 132 133 if (this.evalVisProp('trace')) { 134 this.cloneToBackground(true); 135 } 136 137 return this; 138 }, 139 140 /** 141 * Applies the transformations of the element to {@link JXG.Point#baseElement}. 142 * Point transformations are relative to a base element. 143 * @param {Boolean} fromParent True if the drag comes from a child element. This is the case if a line 144 * through two points is dragged. Otherwise, the element is the drag element and we apply the 145 * the inverse transformation to the baseElement if is different from the element. 146 * @returns {JXG.CoordsElement} Reference to this object. 147 */ 148 updateTransform: function (fromParent) { 149 var c, i; 150 151 if (this.transformations.length === 0 || this.baseElement === null) { 152 return this; 153 } 154 155 this.transformations[0].update(); 156 if (this === this.baseElement) { 157 // Case of bindTo 158 c = this.transformations[0].apply(this, "self"); 159 } else { 160 c = this.transformations[0].apply(this.baseElement); 161 } 162 for (i = 1; i < this.transformations.length; i++) { 163 this.transformations[i].update(); 164 c = Mat.matVecMult(this.transformations[i].matrix, c); 165 } 166 this.coords.setCoordinates(Const.COORDS_BY_USER, c); 167 168 return this; 169 }, 170 171 /** 172 * Calls the renderer to update the drawing. 173 * @private 174 */ 175 updateRenderer: function () { 176 this.updateRendererGeneric("updatePoint"); 177 return this; 178 }, 179 180 // documented in JXG.GeometryElement 181 bounds: function () { 182 return this.coords.usrCoords.slice(1).concat(this.coords.usrCoords.slice(1)); 183 }, 184 185 /** 186 * Convert the point to intersection point and update the construction. 187 * To move the point visual onto the intersection, a call of board update is necessary. 188 * 189 * @param {String|Object} el1, el2, i, j The intersecting objects and the numbers. 190 **/ 191 makeIntersection: function (el1, el2, i, j) { 192 var func; 193 194 el1 = this.board.select(el1); 195 el2 = this.board.select(el2); 196 197 func = Geometry.intersectionFunction( 198 this.board, 199 el1, 200 el2, 201 i, 202 j, 203 this.visProp.alwaysintersect 204 ); 205 this.addConstraint([func]); 206 207 try { 208 el1.addChild(this); 209 el2.addChild(this); 210 } catch (e) { 211 throw new Error( 212 "JSXGraph: Can't create 'intersection' with parent types '" + 213 typeof el1 + 214 "' and '" + 215 typeof el2 + 216 "'." 217 ); 218 } 219 220 this.type = Const.OBJECT_TYPE_INTERSECTION; 221 this.elType = "intersection"; 222 this.parents = [el1.id, el2.id, i, j]; 223 224 this.generatePolynomial = function () { 225 var poly1 = el1.generatePolynomial(this), 226 poly2 = el2.generatePolynomial(this); 227 228 if (poly1.length === 0 || poly2.length === 0) { 229 return []; 230 } 231 232 return [poly1[0], poly2[0]]; 233 }; 234 235 this.prepareUpdate().update(); 236 }, 237 238 /** 239 * Set the style of a point. 240 * Used for GEONExT import and should not be used to set the point's face and size. 241 * @param {Number} i Integer to determine the style. 242 * @private 243 */ 244 setStyle: function (i) { 245 var facemap = [ 246 // 0-2 247 "cross", 248 "cross", 249 "cross", 250 // 3-6 251 "circle", 252 "circle", 253 "circle", 254 "circle", 255 // 7-9 256 "square", 257 "square", 258 "square", 259 // 10-12 260 "plus", 261 "plus", 262 "plus" 263 ], 264 sizemap = [ 265 // 0-2 266 2, 3, 4, 267 // 3-6 268 1, 2, 3, 4, 269 // 7-9 270 2, 3, 4, 271 // 10-12 272 2, 3, 4 273 ]; 274 275 this.visProp.face = facemap[i]; 276 this.visProp.size = sizemap[i]; 277 278 this.board.renderer.changePointStyle(this); 279 return this; 280 }, 281 282 /** 283 * @deprecated Use JXG#normalizePointFace instead 284 * @param s 285 * @returns {*} 286 */ 287 normalizeFace: function (s) { 288 JXG.deprecated("Point.normalizeFace()", "JXG.normalizePointFace()"); 289 return Options.normalizePointFace(s); 290 }, 291 292 /** 293 * Set the face of a point element. 294 * @param {String} f String which determines the face of the point. See {@link JXG.GeometryElement#face} for a list of available faces. 295 * @see JXG.GeometryElement#face 296 * @deprecated Use setAttribute() 297 */ 298 face: function (f) { 299 JXG.deprecated("Point.face()", "Point.setAttribute()"); 300 this.setAttribute({ face: f }); 301 }, 302 303 /** 304 * Set the size of a point element 305 * @param {Number} s Integer which determines the size of the point. 306 * @see JXG.GeometryElement#size 307 * @deprecated Use setAttribute() 308 */ 309 size: function (s) { 310 JXG.deprecated("Point.size()", "Point.setAttribute()"); 311 this.setAttribute({ size: s }); 312 }, 313 314 /** 315 * Test if the point is on (is incident with) element "el". 316 * 317 * @param {JXG.GeometryElement} el 318 * @param {Number} tol 319 * @returns {Boolean} 320 * 321 * @example 322 * var circ = board.create('circle', [[-2, -2], 1]); 323 * var seg = board.create('segment', [[-1, -3], [0,0]]); 324 * var line = board.create('line', [[1, 3], [2, -2]]); 325 * var po = board.create('point', [-1, 0], {color: 'blue'}); 326 * var curve = board.create('functiongraph', ['sin(x)'], {strokeColor: 'blue'}); 327 * var pol = board.create('polygon', [[2,2], [4,2], [4,3]], {strokeColor: 'blue'}); 328 * 329 * var point = board.create('point', [-1, 1], { 330 * attractors: [line, seg, circ, po, curve, pol], 331 * attractorDistance: 0.2 332 * }); 333 * 334 * var txt = board.create('text', [-4, 3, function() { 335 * return 'point on line: ' + point.isOn(line) + '<br>' + 336 * 'point on seg: ' + point.isOn(seg) + '<br>' + 337 * 'point on circ = ' + point.isOn(circ) + '<br>' + 338 * 'point on point = ' + point.isOn(po) + '<br>' + 339 * 'point on curve = ' + point.isOn(curve) + '<br>' + 340 * 'point on polygon = ' + point.isOn(pol) + '<br>'; 341 * }]); 342 * 343 * </pre><div id="JXG6c7d7404-758a-44eb-802c-e9644b9fab71" class="jxgbox" style="width: 300px; height: 300px;"></div> 344 * <script type="text/javascript"> 345 * (function() { 346 * var board = JXG.JSXGraph.initBoard('JXG6c7d7404-758a-44eb-802c-e9644b9fab71', 347 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 348 * var circ = board.create('circle', [[-2, -2], 1]); 349 * var seg = board.create('segment', [[-1, -3], [0,0]]); 350 * var line = board.create('line', [[1, 3], [2, -2]]); 351 * var po = board.create('point', [-1, 0], {color: 'blue'}); 352 * var curve = board.create('functiongraph', ['sin(x)'], {strokeColor: 'blue'}); 353 * var pol = board.create('polygon', [[2,2], [4,2], [4,3]], {strokeColor: 'blue'}); 354 * 355 * var point = board.create('point', [-1, 1], { 356 * attractors: [line, seg, circ, po, curve, pol], 357 * attractorDistance: 0.2 358 * }); 359 * 360 * var txt = board.create('text', [-4, 3, function() { 361 * return 'point on line: ' + point.isOn(line) + '<br>' + 362 * 'point on seg: ' + point.isOn(seg) + '<br>' + 363 * 'point on circ = ' + point.isOn(circ) + '<br>' + 364 * 'point on point = ' + point.isOn(po) + '<br>' + 365 * 'point on curve = ' + point.isOn(curve) + '<br>' + 366 * 'point on polygon = ' + point.isOn(pol) + '<br>'; 367 * }]); 368 * 369 * })(); 370 * 371 * </script><pre> 372 * 373 */ 374 isOn: function (el, tol) { 375 var arr, crds; 376 377 tol = tol || Mat.eps; 378 379 if (Type.isPoint(el)) { 380 return this.Dist(el) < tol; 381 } else if (el.elementClass === Const.OBJECT_CLASS_LINE) { 382 if (el.elType === "segment" && !this.evalVisProp('alwaysintersect')) { 383 arr = JXG.Math.Geometry.projectCoordsToSegment( 384 this.coords.usrCoords, 385 el.point1.coords.usrCoords, 386 el.point2.coords.usrCoords 387 ); 388 if ( 389 arr[1] >= 0 && 390 arr[1] <= 1 && 391 Geometry.distPointLine(this.coords.usrCoords, el.stdform) < tol 392 ) { 393 return true; 394 } else { 395 return false; 396 } 397 } else { 398 return Geometry.distPointLine(this.coords.usrCoords, el.stdform) < tol; 399 } 400 } else if (el.elementClass === Const.OBJECT_CLASS_CIRCLE) { 401 if (el.evalVisProp('hasinnerpoints')) { 402 return this.Dist(el.center) < el.Radius() + tol; 403 } 404 return Math.abs(this.Dist(el.center) - el.Radius()) < tol; 405 } else if (el.elementClass === Const.OBJECT_CLASS_CURVE) { 406 crds = Geometry.projectPointToCurve(this, el, this.board)[0]; 407 return Geometry.distance(this.coords.usrCoords, crds.usrCoords, 3) < tol; 408 } else if (el.type === Const.OBJECT_TYPE_POLYGON) { 409 if (el.evalVisProp('hasinnerpoints')) { 410 if ( 411 el.pnpoly( 412 this.coords.usrCoords[1], 413 this.coords.usrCoords[2], 414 JXG.COORDS_BY_USER 415 ) 416 ) { 417 return true; 418 } 419 } 420 arr = Geometry.projectCoordsToPolygon(this.coords.usrCoords, el); 421 return Geometry.distance(this.coords.usrCoords, arr, 3) < tol; 422 } else if (el.type === Const.OBJECT_TYPE_TURTLE) { 423 crds = Geometry.projectPointToTurtle(this, el, this.board); 424 return Geometry.distance(this.coords.usrCoords, crds.usrCoords, 3) < tol; 425 } 426 427 // TODO: Arc, Sector 428 return false; 429 }, 430 431 // Already documented in GeometryElement 432 cloneToBackground: function () { 433 var copy = Type.getCloneObject(this); 434 435 this.board.renderer.drawPoint(copy); 436 this.traces[copy.id] = copy.rendNode; 437 438 return this; 439 } 440 } 441 ); 442 443 /** 444 * @class This element is used to provide a constructor for a general point. A free point is created if the given parent elements are all numbers 445 * and the property fixed is not set or set to false. If one or more parent elements is not a number but a string containing a GEONE<sub>x</sub>T 446 * constraint or a function the point will be considered as constrained). That means that the user won't be able to change the point's 447 * position directly. 448 * @see Glider for a non-free point that is attached to another geometric element. 449 * @pseudo 450 * @name Point 451 * @augments JXG.Point 452 * @constructor 453 * @type JXG.Point 454 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 455 * @param {Number,string,function_Number,string,function_Number,string,function} z_,x,y Parent elements can be two or three elements of type number, a string containing a GEONE<sub>x</sub>T 456 * constraint, or a function which takes no parameter and returns a number. Every parent element determines one coordinate. If a coordinate is 457 * given by a number, the number determines the initial position of a free point. If given by a string or a function that coordinate will be constrained 458 * that means the user won't be able to change the point's position directly by mouse because it will be calculated automatically depending on the string 459 * or the function's return value. If two parent elements are given the coordinates will be interpreted as 2D affine Euclidean coordinates, if three such 460 * parent elements are given they will be interpreted as homogeneous coordinates. 461 * @param {JXG.Point_JXG.Transformation_Array} Point,Transformation A point can also be created providing a transformation or an array of transformations. 462 * The resulting point is a clone of the base point transformed by the given Transformation. {@see JXG.Transformation}. 463 * 464 * @example 465 * // Create a free point using affine Euclidean coordinates 466 * var p1 = board.create('point', [3.5, 2.0]); 467 * </pre><div class="jxgbox" id="JXG672f1764-7dfa-4abc-a2c6-81fbbf83e44b" style="width: 200px; height: 200px;"></div> 468 * <script type="text/javascript"> 469 * var board = JXG.JSXGraph.initBoard('JXG672f1764-7dfa-4abc-a2c6-81fbbf83e44b', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false}); 470 * var p1 = board.create('point', [3.5, 2.0]); 471 * </script><pre> 472 * @example 473 * // Create a constrained point using anonymous function 474 * var p2 = board.create('point', [3.5, function () { return p1.X(); }]); 475 * </pre><div class="jxgbox" id="JXG4fd4410c-3383-4e80-b1bb-961f5eeef224" style="width: 200px; height: 200px;"></div> 476 * <script type="text/javascript"> 477 * var fpex1_board = JXG.JSXGraph.initBoard('JXG4fd4410c-3383-4e80-b1bb-961f5eeef224', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false}); 478 * var fpex1_p1 = fpex1_board.create('point', [3.5, 2.0]); 479 * var fpex1_p2 = fpex1_board.create('point', [3.5, function () { return fpex1_p1.X(); }]); 480 * </script><pre> 481 * @example 482 * // Create a point using transformations 483 * var trans = board.create('transform', [2, 0.5], {type:'scale'}); 484 * var p3 = board.create('point', [p2, trans]); 485 * </pre><div class="jxgbox" id="JXG630afdf3-0a64-46e0-8a44-f51bd197bb8d" style="width: 400px; height: 400px;"></div> 486 * <script type="text/javascript"> 487 * var fpex2_board = JXG.JSXGraph.initBoard('JXG630afdf3-0a64-46e0-8a44-f51bd197bb8d', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 488 * var fpex2_trans = fpex2_board.create('transform', [2, 0.5], {type:'scale'}); 489 * var fpex2_p2 = fpex2_board.create('point', [3.5, 2.0]); 490 * var fpex2_p3 = fpex2_board.create('point', [fpex2_p2, fpex2_trans]); 491 * </script><pre> 492 */ 493 JXG.createPoint = function (board, parents, attributes) { 494 var el, attr; 495 496 attr = Type.copyAttributes(attributes, board.options, "point"); 497 el = CoordsElement.create(JXG.Point, board, parents, attr); 498 if (!el) { 499 throw new Error( 500 "JSXGraph: Can't create point with parent types '" + 501 typeof parents[0] + 502 "' and '" + 503 typeof parents[1] + 504 "'." + 505 "\nPossible parent types: [x,y], [z,x,y], [element,transformation]" 506 ); 507 } 508 509 return el; 510 }; 511 512 /** 513 * @class This element is used to provide a constructor for a glider point. 514 * @pseudo 515 * @description A glider is a point which lives on another geometric element like a line, circle, curve, turtle. 516 * @name Glider 517 * @augments JXG.Point 518 * @constructor 519 * @type JXG.Point 520 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 521 * @param {Number_Number_Number_JXG.GeometryElement} z_,x_,y_,GlideObject Parent elements can be two or three elements of type number and the object the glider lives on. 522 * The coordinates are completely optional. If not given the origin is used. If you provide two numbers for coordinates they will be interpreted as affine Euclidean 523 * coordinates, otherwise they will be interpreted as homogeneous coordinates. In any case the point will be projected on the glide object. 524 * @example 525 * // Create a glider with user defined coordinates. If the coordinates are not on 526 * // the circle (like in this case) the point will be projected onto the circle. 527 * var p1 = board.create('point', [2.0, 2.0]); 528 * var c1 = board.create('circle', [p1, 2.0]); 529 * var p2 = board.create('glider', [2.0, 1.5, c1]); 530 * </pre><div class="jxgbox" id="JXG4f65f32f-e50a-4b50-9b7c-f6ec41652930" style="width: 300px; height: 300px;"></div> 531 * <script type="text/javascript"> 532 * var gpex1_board = JXG.JSXGraph.initBoard('JXG4f65f32f-e50a-4b50-9b7c-f6ec41652930', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false}); 533 * var gpex1_p1 = gpex1_board.create('point', [2.0, 2.0]); 534 * var gpex1_c1 = gpex1_board.create('circle', [gpex1_p1, 2.0]); 535 * var gpex1_p2 = gpex1_board.create('glider', [2.0, 1.5, gpex1_c1]); 536 * </script><pre> 537 * @example 538 * // Create a glider with default coordinates (1,0,0). Same premises as above. 539 * var p1 = board.create('point', [2.0, 2.0]); 540 * var c1 = board.create('circle', [p1, 2.0]); 541 * var p2 = board.create('glider', [c1]); 542 * </pre><div class="jxgbox" id="JXG4de7f181-631a-44b1-a12f-bc4d995609e8" style="width: 200px; height: 200px;"></div> 543 * <script type="text/javascript"> 544 * var gpex2_board = JXG.JSXGraph.initBoard('JXG4de7f181-631a-44b1-a12f-bc4d995609e8', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false}); 545 * var gpex2_p1 = gpex2_board.create('point', [2.0, 2.0]); 546 * var gpex2_c1 = gpex2_board.create('circle', [gpex2_p1, 2.0]); 547 * var gpex2_p2 = gpex2_board.create('glider', [gpex2_c1]); 548 * </script><pre> 549 *@example 550 * //animate example 2 551 * var p1 = board.create('point', [2.0, 2.0]); 552 * var c1 = board.create('circle', [p1, 2.0]); 553 * var p2 = board.create('glider', [c1]); 554 * var button1 = board.create('button', [1, 7, 'start animation',function(){p2.startAnimation(1,4)}]); 555 * var button2 = board.create('button', [1, 5, 'stop animation',function(){p2.stopAnimation()}]); 556 * </pre><div class="jxgbox" id="JXG4de7f181-631a-44b1-a12f-bc4d133709e8" style="width: 200px; height: 200px;"></div> 557 * <script type="text/javascript"> 558 * var gpex3_board = JXG.JSXGraph.initBoard('JXG4de7f181-631a-44b1-a12f-bc4d133709e8', {boundingbox: [-1, 10, 10, -1], axis: true, showcopyright: false, shownavigation: false}); 559 * var gpex3_p1 = gpex3_board.create('point', [2.0, 2.0]); 560 * var gpex3_c1 = gpex3_board.create('circle', [gpex3_p1, 2.0]); 561 * var gpex3_p2 = gpex3_board.create('glider', [gpex3_c1]); 562 * gpex3_board.create('button', [1, 7, 'start animation',function(){gpex3_p2.startAnimation(1,4)}]); 563 * gpex3_board.create('button', [1, 5, 'stop animation',function(){gpex3_p2.stopAnimation()}]); 564 * </script><pre> 565 */ 566 JXG.createGlider = function (board, parents, attributes) { 567 var el, 568 coords, 569 attr = Type.copyAttributes(attributes, board.options, "glider"); 570 571 if (parents.length === 1) { 572 coords = [0, 0]; 573 } else { 574 coords = parents.slice(0, 2); 575 } 576 el = board.create("point", coords, attr); 577 578 // eltype is set in here 579 el.makeGlider(parents[parents.length - 1]); 580 581 return el; 582 }; 583 584 /** 585 * @class An intersection point is a point which lives on two JSXGraph elements, i.e. it is one point of the set 586 * consisting of the intersection points of the two elements. The following element types can be (mutually) intersected: line, circle, 587 * curve, polygon, polygonal chain. 588 * 589 * @pseudo 590 * @name Intersection 591 * @augments JXG.Point 592 * @constructor 593 * @type JXG.Point 594 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 595 * @param {JXG.Line,JXG.Circle_JXG.Line,JXG.Circle_Number|Function} el1,el2,i The result will be a intersection point on el1 and el2. i determines the 596 * intersection point if two points are available: <ul> 597 * <li>i==0: use the positive square root,</li> 598 * <li>i==1: use the negative square root.</li></ul> 599 * @example 600 * // Create an intersection point of circle and line 601 * var p1 = board.create('point', [4.0, 4.0]); 602 * var c1 = board.create('circle', [p1, 2.0]); 603 * 604 * var p2 = board.create('point', [1.0, 1.0]); 605 * var p3 = board.create('point', [5.0, 3.0]); 606 * var l1 = board.create('line', [p2, p3]); 607 * 608 * var i = board.create('intersection', [c1, l1, 0]); 609 * </pre><div class="jxgbox" id="JXGe5b0e190-5200-4bc3-b995-b6cc53dc5dc0" style="width: 300px; height: 300px;"></div> 610 * <script type="text/javascript"> 611 * var ipex1_board = JXG.JSXGraph.initBoard('JXGe5b0e190-5200-4bc3-b995-b6cc53dc5dc0', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 612 * var ipex1_p1 = ipex1_board.create('point', [4.0, 4.0]); 613 * var ipex1_c1 = ipex1_board.create('circle', [ipex1_p1, 2.0]); 614 * var ipex1_p2 = ipex1_board.create('point', [1.0, 1.0]); 615 * var ipex1_p3 = ipex1_board.create('point', [5.0, 3.0]); 616 * var ipex1_l1 = ipex1_board.create('line', [ipex1_p2, ipex1_p3]); 617 * var ipex1_i = ipex1_board.create('intersection', [ipex1_c1, ipex1_l1, 0]); 618 * </script><pre> 619 */ 620 JXG.createIntersectionPoint = function (board, parents, attributes) { 621 var el, el1, el2, func, 622 i, j, 623 attr = Type.copyAttributes(attributes, board.options, "intersection"); 624 625 // make sure we definitely have the indices 626 parents.push(0, 0); 627 628 el1 = board.select(parents[0]); 629 el2 = board.select(parents[1]); 630 631 i = parents[2] || 0; 632 j = parents[3] || 0; 633 634 el = board.create("point", [0, 0, 0], attr); 635 636 // el.visProp.alwaysintersect is evaluated as late as in the returned function 637 func = Geometry.intersectionFunction(board, el1, el2, i, j, el.visProp.alwaysintersect); 638 el.addConstraint([func]); 639 640 try { 641 el1.addChild(el); 642 el2.addChild(el); 643 } catch (e) { 644 throw new Error( 645 "JSXGraph: Can't create 'intersection' with parent types '" + 646 typeof parents[0] + 647 "' and '" + 648 typeof parents[1] + 649 "'." 650 ); 651 } 652 653 el.type = Const.OBJECT_TYPE_INTERSECTION; 654 el.elType = "intersection"; 655 el.setParents([el1.id, el2.id]); 656 657 /** 658 * Array of length 2 containing the numbers i and j. 659 * The intersection point is i-th intersection point. 660 * j is unused. 661 * @type Array 662 * @name intersectionNumbers 663 * @memberOf Intersection 664 * @private 665 */ 666 el.intersectionNumbers = [i, j]; 667 el.getParents = function () { 668 return this.parents.concat(this.intersectionNumbers); 669 }; 670 671 el.generatePolynomial = function () { 672 var poly1 = el1.generatePolynomial(el), 673 poly2 = el2.generatePolynomial(el); 674 675 if (poly1.length === 0 || poly2.length === 0) { 676 return []; 677 } 678 679 return [poly1[0], poly2[0]]; 680 }; 681 682 return el; 683 }; 684 685 /** 686 * @class This element is used to provide a constructor for the "other" intersection point. 687 * @pseudo 688 * @description If two elements of type curve, circle or line intersect in more than one point, with this element it is possible 689 * to construct the "other" intersection. This is a an intersection which is different from a supplied point or different from any 690 * point in an array of supplied points. This might be helpful in situtations where one intersection point is already part of the construction 691 * or in situtation where the order of the intersection points changes while interacting with the construction. 692 * 693 * @name OtherIntersection 694 * @augments JXG.Point 695 * @constructor 696 * @type JXG.Point 697 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 698 * @param {JXG.Line,JXG.Circle_JXG.Line,JXG.Circle_JXG.Point,Array} el1,el2,p Two elements which are intersected and a point or an array of points 699 * which have to be different from the new intersection point. 700 * 701 * @example 702 * // Create an intersection point of circle and line 703 * var p1 = board.create('point', [2.0, 2.0]); 704 * var c1 = board.create('circle', [p1, 2.0]); 705 * 706 * var p2 = board.create('point', [2.0, 2.0]); 707 * var p3 = board.create('point', [2.0, 2.0]); 708 * var l1 = board.create('line', [p2, p3]); 709 * 710 * var p1 = board.create('intersection', [c1, l1, 0]); 711 * var p2 = board.create('otherintersection', [c1, l1, p1]); 712 * </pre><div class="jxgbox" id="JXG45e25f12-a1de-4257-a466-27a2ae73614c" style="width: 300px; height: 300px;"></div> 713 * <script type="text/javascript"> 714 * var ipex2_board = JXG.JSXGraph.initBoard('JXG45e25f12-a1de-4257-a466-27a2ae73614c', {boundingbox: [-1, 7, 7, -1], axis: false, showcopyright: false, shownavigation: false}); 715 * var ipex2_p1 = ipex2_board.create('point', [4.0, 4.0]); 716 * var ipex2_c1 = ipex2_board.create('circle', [ipex2_p1, 2.0]); 717 * var ipex2_p2 = ipex2_board.create('point', [1.0, 1.0]); 718 * var ipex2_p3 = ipex2_board.create('point', [5.0, 3.0]); 719 * var ipex2_l1 = ipex2_board.create('line', [ipex2_p2, ipex2_p3]); 720 * var ipex2_i = ipex2_board.create('intersection', [ipex2_c1, ipex2_l1, 0], {name:'D'}); 721 * var ipex2_j = ipex2_board.create('otherintersection', [ipex2_c1, ipex2_l1, ipex2_i], {name:'E'}); 722 * </script><pre> 723 * 724 * @example 725 * // circle / circle 726 * var c1 = board.create('circle', [[0, 0], 3]); 727 * var c2 = board.create('circle', [[2, 2], 3]); 728 * 729 * var p1 = board.create('intersection', [c1, c2, 0]); 730 * var p2 = board.create('otherintersection', [c1, c2, p1]); 731 * 732 * </pre><div id="JXGdb5c974c-3092-4cdf-b5ef-d0af4a912581" class="jxgbox" style="width: 300px; height: 300px;"></div> 733 * <script type="text/javascript"> 734 * (function() { 735 * var board = JXG.JSXGraph.initBoard('JXGdb5c974c-3092-4cdf-b5ef-d0af4a912581', 736 * {boundingbox: [-8, 8, 8,-8], axis: false, showcopyright: false, shownavigation: false}); 737 * var c1 = board.create('circle', [[0, 0], 3]); 738 * var c2 = board.create('circle', [[2, 2], 3]); 739 * 740 * var p1 = board.create('intersection', [c1, c2, 0]); 741 * var p2 = board.create('otherintersection', [c1, c2, p1]); 742 * })(); 743 * </script><pre> 744 * 745 * @example 746 * // curve / line 747 * var curve = board.create('implicitcurve', ['-(y**2) + x**3 - 2 * x + 1'], { strokeWidth: 2 }); 748 * var A = board.create('glider', [-1.5, 1, curve]); 749 * var B = board.create('glider', [0.5, 0.5, curve]); 750 * var line = board.create('line', [A, B], { color: 'black', strokeWidth: 1 }); 751 * var C = board.create('otherintersection', [curve, line, [A, B]], {precision: 0.01}); 752 * var D = board.create('point', [() => C.X(), () => -C.Y()], { name: '-C = A + B' }); 753 * 754 * </pre><div id="JXG033f15b0-f5f1-4003-ab6a-b7e13e867fbd" class="jxgbox" style="width: 300px; height: 300px;"></div> 755 * <script type="text/javascript"> 756 * (function() { 757 * var board = JXG.JSXGraph.initBoard('JXG033f15b0-f5f1-4003-ab6a-b7e13e867fbd', 758 * {boundingbox: [-2, 2, 2, -2], axis: false, showcopyright: false, shownavigation: false}); 759 * var curve = board.create('implicitcurve', ['-(y**2) + x**3 - 2 * x + 1'], { strokeWidth: 2 }); 760 * var A = board.create('glider', [-1.5, 1, curve]); 761 * var B = board.create('glider', [0.5, 0.5, curve]); 762 * var line = board.create('line', [A, B], { color: 'black', strokeWidth: 1 }); 763 * var C = board.create('otherintersection', [curve, line, [A, B]], {precision: 0.01}); 764 * var D = board.create('point', [() => C.X(), () => -C.Y()], { name: '-C = A + B' }); 765 * })(); 766 * </script><pre> 767 * 768 * @example 769 * // curve / curve 770 * var c1 = board.create('functiongraph', ['x**2 - 3'], { strokeWidth: 2 }); 771 * var A = board.create('point', [0, 2]); 772 * var c2 = board.create('functiongraph', [(x) => -(x**2) + 2 * A.X() * x + A.Y() - A.X()**2], { strokeWidth: 2 }); 773 * var p1 = board.create('intersection', [c1, c2]); 774 * var p2 = board.create('otherintersection', [c1, c2, [p1]]); 775 * 776 * </pre><div id="JXG29359aa9-3066-4f45-9e5d-d74201b991d3" class="jxgbox" style="width: 300px; height: 300px;"></div> 777 * <script type="text/javascript"> 778 * (function() { 779 * var board = JXG.JSXGraph.initBoard('JXG29359aa9-3066-4f45-9e5d-d74201b991d3', 780 * {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright: false, shownavigation: false}); 781 * var c1 = board.create('functiongraph', ['x**2 - 3'], { strokeWidth: 2 }); 782 * var A = board.create('point', [0, 2]); 783 * var c2 = board.create('functiongraph', [(x) => -(x**2) + 2 * A.X() * x + A.Y() - A.X()**2], { strokeWidth: 2 }); 784 * var p1 = board.create('intersection', [c1, c2]); 785 * var p2 = board.create('otherintersection', [c1, c2, [p1]]); 786 * })(); 787 * </script><pre> 788 * 789 */ 790 JXG.createOtherIntersectionPoint = function (board, parents, attributes) { 791 var el, el1, el2, i, 792 others, func, input, 793 isGood = true, 794 attr = Type.copyAttributes(attributes, board.options, 'otherintersection'); 795 796 if (parents.length !== 3) { 797 isGood = false; 798 } else { 799 el1 = board.select(parents[0]); 800 el2 = board.select(parents[1]); 801 if (Type.isArray(parents[2])) { 802 others = parents[2]; 803 } else { 804 others = [parents[2]]; 805 } 806 807 for (i = 0; i < others.length; i++) { 808 others[i] = board.select(others[i]); 809 if (!Type.isPoint(others[i])) { 810 isGood = false; 811 break; 812 } 813 } 814 if (isGood) { 815 input = [el1, el2]; 816 // Sort parent elements in order: curve, circle, line 817 input.sort(function (a, b) { return b.elementClass - a.elementClass; }); 818 819 // Two lines are forbidden: 820 if ([Const.OBJECT_CLASS_CIRCLE, Const.OBJECT_CLASS_CURVE].indexOf(input[0].elementClass) < 0) { 821 isGood = false; 822 } else if ([Const.OBJECT_CLASS_CIRCLE, Const.OBJECT_CLASS_CURVE, Const.OBJECT_CLASS_LINE].indexOf(input[1].elementClass) < 0) { 823 isGood = false; 824 } 825 } 826 } 827 828 if (!isGood) { 829 throw new Error( 830 "JSXGraph: Can't create 'other intersection point' with parent types '" + 831 typeof parents[0] + "', '" + typeof parents[1] + "'and '" + typeof parents[2] + "'." + 832 "\nPossible parent types: [circle|curve|line,circle|curve|line, point], not two lines" 833 ); 834 } 835 836 el = board.create('point', [0, 0, 0], attr); 837 // el.visProp.alwaysintersect is evaluated as late as in the returned function 838 func = Geometry.otherIntersectionFunction(input, others, el.visProp.alwaysintersect, el.visProp.precision); 839 el.addConstraint([func]); 840 841 el.type = Const.OBJECT_TYPE_INTERSECTION; 842 el.elType = "otherintersection"; 843 el.setParents([el1.id, el2.id]); 844 el.addParents(others); 845 846 el1.addChild(el); 847 el2.addChild(el); 848 849 if (el1.elementClass === Const.OBJECT_CLASS_CIRCLE) { 850 // circle, circle|line 851 el.generatePolynomial = function () { 852 var poly1 = el1.generatePolynomial(el), 853 poly2 = el2.generatePolynomial(el); 854 855 if (poly1.length === 0 || poly2.length === 0) { 856 return []; 857 } 858 859 return [poly1[0], poly2[0]]; 860 }; 861 } 862 863 return el; 864 }; 865 866 /** 867 * @class This element is used to provide a constructor for the pole point of a line with respect to a conic or a circle. 868 * @pseudo 869 * @description The pole point is the unique reciprocal relationship of a line with respect to a conic. 870 * The lines tangent to the intersections of a conic and a line intersect at the pole point of that line with respect to that conic. 871 * A line tangent to a conic has the pole point of that line with respect to that conic as the tangent point. 872 * See {@link https://en.wikipedia.org/wiki/Pole_and_polar} for more information on pole and polar. 873 * @name PolePoint 874 * @augments JXG.Point 875 * @constructor 876 * @type JXG.Point 877 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 878 * @param {JXG.Conic,JXG.Circle_JXG.Point} el1,el2 or 879 * @param {JXG.Point_JXG.Conic,JXG.Circle} el1,el2 The result will be the pole point of the line with respect to the conic or the circle. 880 * @example 881 * // Create the pole point of a line with respect to a conic 882 * var p1 = board.create('point', [-1, 2]); 883 * var p2 = board.create('point', [ 1, 4]); 884 * var p3 = board.create('point', [-1,-2]); 885 * var p4 = board.create('point', [ 0, 0]); 886 * var p5 = board.create('point', [ 4,-2]); 887 * var c1 = board.create('conic',[p1,p2,p3,p4,p5]); 888 * var p6 = board.create('point', [-1, 4]); 889 * var p7 = board.create('point', [2, -2]); 890 * var l1 = board.create('line', [p6, p7]); 891 * var p8 = board.create('polepoint', [c1, l1]); 892 * </pre><div class="jxgbox" id="JXG7b7233a0-f363-47dd-9df5-8018d0d17a98" class="jxgbox" style="width:400px; height:400px;"></div> 893 * <script type='text/javascript'> 894 * var ppex1_board = JXG.JSXGraph.initBoard('JXG7b7233a0-f363-47dd-9df5-8018d0d17a98', {boundingbox: [-3, 5, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 895 * var ppex1_p1 = ppex1_board.create('point', [-1, 2]); 896 * var ppex1_p2 = ppex1_board.create('point', [ 1, 4]); 897 * var ppex1_p3 = ppex1_board.create('point', [-1,-2]); 898 * var ppex1_p4 = ppex1_board.create('point', [ 0, 0]); 899 * var ppex1_p5 = ppex1_board.create('point', [ 4,-2]); 900 * var ppex1_c1 = ppex1_board.create('conic',[ppex1_p1,ppex1_p2,ppex1_p3,ppex1_p4,ppex1_p5]); 901 * var ppex1_p6 = ppex1_board.create('point', [-1, 4]); 902 * var ppex1_p7 = ppex1_board.create('point', [2, -2]); 903 * var ppex1_l1 = ppex1_board.create('line', [ppex1_p6, ppex1_p7]); 904 * var ppex1_p8 = ppex1_board.create('polepoint', [ppex1_c1, ppex1_l1]); 905 * </script><pre> 906 * @example 907 * // Create the pole point of a line with respect to a circle 908 * var p1 = board.create('point', [1, 1]); 909 * var p2 = board.create('point', [2, 3]); 910 * var c1 = board.create('circle',[p1,p2]); 911 * var p3 = board.create('point', [-1, 4]); 912 * var p4 = board.create('point', [4, -1]); 913 * var l1 = board.create('line', [p3, p4]); 914 * var p5 = board.create('polepoint', [c1, l1]); 915 * </pre><div class="jxgbox" id="JXG7b7233a0-f363-47dd-9df5-9018d0d17a98" class="jxgbox" style="width:400px; height:400px;"></div> 916 * <script type='text/javascript'> 917 * var ppex2_board = JXG.JSXGraph.initBoard('JXG7b7233a0-f363-47dd-9df5-9018d0d17a98', {boundingbox: [-3, 7, 7, -3], axis: true, showcopyright: false, shownavigation: false}); 918 * var ppex2_p1 = ppex2_board.create('point', [1, 1]); 919 * var ppex2_p2 = ppex2_board.create('point', [2, 3]); 920 * var ppex2_c1 = ppex2_board.create('circle',[ppex2_p1,ppex2_p2]); 921 * var ppex2_p3 = ppex2_board.create('point', [-1, 4]); 922 * var ppex2_p4 = ppex2_board.create('point', [4, -1]); 923 * var ppex2_l1 = ppex2_board.create('line', [ppex2_p3, ppex2_p4]); 924 * var ppex2_p5 = ppex2_board.create('polepoint', [ppex2_c1, ppex2_l1]); 925 * </script><pre> 926 */ 927 JXG.createPolePoint = function (board, parents, attributes) { 928 var el, 929 el1, 930 el2, 931 firstParentIsConic, 932 secondParentIsConic, 933 firstParentIsLine, 934 secondParentIsLine; 935 936 if (parents.length > 1) { 937 firstParentIsConic = 938 parents[0].type === Const.OBJECT_TYPE_CONIC || 939 parents[0].elementClass === Const.OBJECT_CLASS_CIRCLE; 940 secondParentIsConic = 941 parents[1].type === Const.OBJECT_TYPE_CONIC || 942 parents[1].elementClass === Const.OBJECT_CLASS_CIRCLE; 943 944 firstParentIsLine = parents[0].elementClass === Const.OBJECT_CLASS_LINE; 945 secondParentIsLine = parents[1].elementClass === Const.OBJECT_CLASS_LINE; 946 } 947 948 /* if (parents.length !== 2 || !(( 949 parents[0].type === Const.OBJECT_TYPE_CONIC || 950 parents[0].elementClass === Const.OBJECT_CLASS_CIRCLE) && 951 parents[1].elementClass === Const.OBJECT_CLASS_LINE || 952 parents[0].elementClass === Const.OBJECT_CLASS_LINE && ( 953 parents[1].type === Const.OBJECT_TYPE_CONIC || 954 parents[1].elementClass === Const.OBJECT_CLASS_CIRCLE))) {*/ 955 if ( 956 parents.length !== 2 || 957 !( 958 (firstParentIsConic && secondParentIsLine) || 959 (firstParentIsLine && secondParentIsConic) 960 ) 961 ) { 962 // Failure 963 throw new Error( 964 "JSXGraph: Can't create 'pole point' with parent types '" + 965 typeof parents[0] + 966 "' and '" + 967 typeof parents[1] + 968 "'." + 969 "\nPossible parent type: [conic|circle,line], [line,conic|circle]" 970 ); 971 } 972 973 if (secondParentIsLine) { 974 el1 = board.select(parents[0]); 975 el2 = board.select(parents[1]); 976 } else { 977 el1 = board.select(parents[1]); 978 el2 = board.select(parents[0]); 979 } 980 981 el = board.create( 982 "point", 983 [ 984 function () { 985 var q = el1.quadraticform, 986 s = el2.stdform.slice(0, 3); 987 988 return [ 989 JXG.Math.Numerics.det([s, q[1], q[2]]), 990 JXG.Math.Numerics.det([q[0], s, q[2]]), 991 JXG.Math.Numerics.det([q[0], q[1], s]) 992 ]; 993 } 994 ], 995 attributes 996 ); 997 998 el.elType = "polepoint"; 999 el.setParents([el1.id, el2.id]); 1000 1001 el1.addChild(el); 1002 el2.addChild(el); 1003 1004 return el; 1005 }; 1006 1007 JXG.registerElement("point", JXG.createPoint); 1008 JXG.registerElement("glider", JXG.createGlider); 1009 JXG.registerElement("intersection", JXG.createIntersectionPoint); 1010 JXG.registerElement("otherintersection", JXG.createOtherIntersectionPoint); 1011 JXG.registerElement("polepoint", JXG.createPolePoint); 1012 1013 export default JXG.Point; 1014 // export default { 1015 // Point: JXG.Point, 1016 // createPoint: JXG.createPoint, 1017 // createGlider: JXG.createGlider, 1018 // createIntersection: JXG.createIntersectionPoint, 1019 // createOtherIntersection: JXG.createOtherIntersectionPoint, 1020 // createPolePoint: JXG.createPolePoint 1021 // }; 1022