1 /* 2 Copyright 2008-2023 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*/ 33 /*jslint nomen: true, plusplus: true*/ 34 35 /** 36 * @fileoverview This file contains our composition elements, i.e. these elements are mostly put together 37 * from one or more {@link JXG.GeometryElement} but with a special meaning. E.g. the midpoint element is contained here 38 * and this is just a {@link JXG.Point} with coordinates dependent from two other points. Currently in this file the 39 * following compositions can be found: <ul> 40 * <li>{@link Arrowparallel} (currently private)</li> 41 * <li>{@link Bisector}</li> 42 * <li>{@link Msector}</li> 43 * <li>{@link Circumcircle}</li> 44 * <li>{@link Circumcirclemidpoint}</li> 45 * <li>{@link Integral}</li> 46 * <li>{@link Midpoint}</li> 47 * <li>{@link Mirrorpoint}</li> 48 * <li>{@link Normal}</li> 49 * <li>{@link Orthogonalprojection}</li> 50 * <li>{@link Parallel}</li> 51 * <li>{@link Perpendicular}</li> 52 * <li>{@link Perpendicularpoint}</li> 53 * <li>{@link Perpendicularsegment}</li> 54 * <li>{@link Reflection}</li></ul> 55 */ 56 57 import JXG from "../jxg"; 58 import Mat from "../math/math"; 59 import Geometry from "../math/geometry"; 60 import Numerics from "../math/numerics"; 61 import Coords from "../base/coords"; 62 import Type from "../utils/type"; 63 import Const from "../base/constants"; 64 // import Point from "../base/point"; 65 // import Line from "../base/line"; 66 // import Circle from "../base/circle"; 67 // import Transform from "../base/transformation"; 68 import Composition from "../base/composition"; 69 // import Curve from "../base/curve"; 70 // import Polygon from "../base/polygon"; 71 72 /** 73 * @class This is used to construct a point that is the orthogonal projection of a point to a line. 74 * @pseudo 75 * @description An orthogonal projection is given by a point and a line. It is determined by projecting the given point 76 * orthogonal onto the given line. 77 * @constructor 78 * @name Orthogonalprojection 79 * @type JXG.Point 80 * @augments JXG.Point 81 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 82 * @param {JXG.Line_JXG.Point} p,l The constructed point is the orthogonal projection of p onto l. 83 * @example 84 * var p1 = board.create('point', [0.0, 4.0]); 85 * var p2 = board.create('point', [6.0, 1.0]); 86 * var l1 = board.create('line', [p1, p2]); 87 * var p3 = board.create('point', [3.0, 3.0]); 88 * 89 * var pp1 = board.create('orthogonalprojection', [p3, l1]); 90 * </pre><div class="jxgbox" id="JXG7708b215-39fa-41b6-b972-19d73d77d791" style="width: 400px; height: 400px;"></div> 91 * <script type="text/javascript"> 92 * var ppex1_board = JXG.JSXGraph.initBoard('JXG7708b215-39fa-41b6-b972-19d73d77d791', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 93 * var ppex1_p1 = ppex1_board.create('point', [0.0, 4.0]); 94 * var ppex1_p2 = ppex1_board.create('point', [6.0, 1.0]); 95 * var ppex1_l1 = ppex1_board.create('line', [ppex1_p1, ppex1_p2]); 96 * var ppex1_p3 = ppex1_board.create('point', [3.0, 3.0]); 97 * var ppex1_pp1 = ppex1_board.create('orthogonalprojection', [ppex1_p3, ppex1_l1]); 98 * </script><pre> 99 */ 100 JXG.createOrthogonalProjection = function (board, parents, attributes) { 101 var l, p, t, attr; 102 103 parents[0] = board.select(parents[0]); 104 parents[1] = board.select(parents[1]); 105 106 if ( 107 Type.isPointType(board, parents[0]) && 108 parents[1].elementClass === Const.OBJECT_CLASS_LINE 109 ) { 110 p = Type.providePoints(board, [parents[0]], attributes, "point")[0]; 111 l = parents[1]; 112 } else if ( 113 Type.isPointType(board, parents[1]) && 114 parents[0].elementClass === Const.OBJECT_CLASS_LINE 115 ) { 116 p = Type.providePoints(board, [parents[1]], attributes, "point")[0]; 117 l = parents[0]; 118 } else { 119 throw new Error( 120 "JSXGraph: Can't create perpendicular point with parent types '" + 121 typeof parents[0] + 122 "' and '" + 123 typeof parents[1] + 124 "'." + 125 "\nPossible parent types: [point,line]" 126 ); 127 } 128 129 attr = Type.copyAttributes(attributes, board.options, "orthogonalprojection"); 130 131 /** 132 * @type JXG.Element 133 * @ignore 134 */ 135 t = board.create( 136 "point", 137 [ 138 function () { 139 return Geometry.projectPointToLine(p, l, board); 140 } 141 ], 142 attr 143 ); 144 145 if (Type.exists(p._is_new)) { 146 t.addChild(p); 147 delete p._is_new; 148 } else { 149 p.addChild(t); 150 } 151 l.addChild(t); 152 153 t.elType = "orthogonalprojection"; 154 t.setParents([p.id, t.id]); 155 156 t.update(); 157 158 /** 159 * Used to generate a polynomial for the orthogonal projection 160 * @name Orthogonalprojection#generatePolynomial 161 * @returns {Array} An array containing the generated polynomial. 162 * @private 163 * @function 164 * @ignore 165 */ 166 t.generatePolynomial = function () { 167 /* 168 * Perpendicular takes point P and line L and creates point T and line M: 169 * 170 * | M 171 * | 172 * x P (p1,p2) 173 * | 174 * | 175 * L | 176 * ----------x-------------x------------------------x-------- 177 * A (a1,a2) |T (t1,t2) B (b1,b2) 178 * | 179 * | 180 * 181 * So we have two conditions: 182 * 183 * (a) AT || TB (collinearity condition) 184 * (b) PT _|_ AB (orthogonality condition) 185 * 186 * a2-t2 t2-b2 187 * ------- = ------- (1) 188 * a1-t1 t1-b1 189 * 190 * p2-t2 a1-b1 191 * ------- = - ------- (2) 192 * p1-t1 a2-b2 193 * 194 * Multiplying (1) and (2) with denominators and simplifying gives 195 * 196 * a2t1 - a2b1 + t2b1 - a1t2 + a1b2 - t1b2 = 0 (1') 197 * 198 * p2a2 - p2b2 - t2a2 + t2b2 + p1a1 - p1b1 - t1a1 + t1b1 = 0 (2') 199 * 200 */ 201 202 var a1 = l.point1.symbolic.x, 203 a2 = l.point1.symbolic.y, 204 b1 = l.point2.symbolic.x, 205 b2 = l.point2.symbolic.y, 206 p1 = p.symbolic.x, 207 p2 = p.symbolic.y, 208 t1 = t.symbolic.x, 209 t2 = t.symbolic.y, 210 poly1 = "(" + a2 + ")*(" + t1 + ")-(" + a2 + ")*(" + b1 + ")+(" + t2 + ")*(" + b1 + ")-(" + a1 + ")*(" + t2 + ")+(" + a1 + ")*(" + 211 b2 + ")-(" + t1 + ")*(" + b2 + ")", 212 poly2 = "(" + p2 + ")*(" + a2 + ")-(" + p2 + ")*(" + b2 + ")-(" + t2 + ")*(" + a2 + ")+(" + t2 + ")*(" + b2 + ")+(" + p1 + ")*(" + 213 a1 + ")-(" + p1 + ")*(" + b1 + ")-(" + t1 + ")*(" + a1 + ")+(" + t1 + ")*(" + b1 + ")"; 214 215 return [poly1, poly2]; 216 }; 217 218 return t; 219 }; 220 221 /** 222 223 * @class This element is used to provide a constructor for a perpendicular. 224 * @pseudo 225 * @description A perpendicular is a composition of two elements: a line and a point. The line is orthogonal 226 * to a given line and contains a given point. 227 * @name Perpendicular 228 * @constructor 229 * @type JXG.Line 230 * @augments Segment 231 * @returns A {@link JXG.Line} object through the given point that is orthogonal to the given line. 232 * @throws {Error} If the elements cannot be constructed with the given parent objects an exception is thrown. 233 * @param {JXG.Line_JXG.Point} l,p The perpendicular line will be orthogonal to l and 234 * will contain p. 235 * @example 236 * // Create a perpendicular 237 * var p1 = board.create('point', [0.0, 2.0]); 238 * var p2 = board.create('point', [2.0, 1.0]); 239 * var l1 = board.create('line', [p1, p2]); 240 * 241 * var p3 = board.create('point', [3.0, 3.0]); 242 * var perp1 = board.create('perpendicular', [l1, p3]); 243 * </pre><div class="jxgbox" id="JXGd5b78842-7b27-4d37-b608-d02519e6cd03" style="width: 400px; height: 400px;"></div> 244 * <script type="text/javascript"> 245 * var pex1_board = JXG.JSXGraph.initBoard('JXGd5b78842-7b27-4d37-b608-d02519e6cd03', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 246 * var pex1_p1 = pex1_board.create('point', [0.0, 2.0]); 247 * var pex1_p2 = pex1_board.create('point', [2.0, 1.0]); 248 * var pex1_l1 = pex1_board.create('line', [pex1_p1, pex1_p2]); 249 * var pex1_p3 = pex1_board.create('point', [3.0, 3.0]); 250 * var pex1_perp1 = pex1_board.create('perpendicular', [pex1_l1, pex1_p3]); 251 * </script><pre> 252 */ 253 JXG.createPerpendicular = function (board, parents, attributes) { 254 var p, l, pd, attr; 255 256 parents[0] = board.select(parents[0]); 257 parents[1] = board.select(parents[1]); 258 259 if ( 260 Type.isPointType(board, parents[0]) && 261 parents[1].elementClass === Const.OBJECT_CLASS_LINE 262 ) { 263 l = parents[1]; 264 p = Type.providePoints(board, [parents[0]], attributes, "point")[0]; 265 } else if ( 266 Type.isPointType(board, parents[1]) && 267 parents[0].elementClass === Const.OBJECT_CLASS_LINE 268 ) { 269 l = parents[0]; 270 p = Type.providePoints(board, [parents[1]], attributes, "point")[0]; 271 } else { 272 throw new Error( 273 "JSXGraph: Can't create perpendicular with parent types '" + 274 typeof parents[0] + 275 "' and '" + 276 typeof parents[1] + 277 "'." + 278 "\nPossible parent types: [line,point]" 279 ); 280 } 281 282 attr = Type.copyAttributes(attributes, board.options, "perpendicular"); 283 pd = JXG.createLine( 284 board, 285 [ 286 function () { 287 return l.stdform[2] * p.X() - l.stdform[1] * p.Y(); 288 }, 289 function () { 290 return -l.stdform[2] * p.Z(); 291 }, 292 function () { 293 return l.stdform[1] * p.Z(); 294 } 295 ], 296 attr 297 ); 298 299 pd.elType = "perpendicular"; 300 pd.setParents([l.id, p.id]); 301 302 if (Type.exists(p._is_new)) { 303 pd.addChild(p); 304 delete p._is_new; 305 } else { 306 p.addChild(pd); 307 } 308 l.addChild(pd); 309 310 return pd; 311 }; 312 313 /** 314 * @class This is used to construct a perpendicular point. 315 * @pseudo 316 * @description A perpendicular point is given by a point and a line. It is determined by projecting the given point 317 * orthogonal onto the given line. This element should be used in GEONExTReader only. All other applications should 318 * use orthogonal projection {@link Orthogonalprojection}. 319 * @constructor 320 * @name PerpendicularPoint 321 * @type JXG.Point 322 * @augments JXG.Point 323 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 324 * @param {JXG.Line_JXG.Point} p,l The constructed point is the orthogonal projection of p onto l. 325 * @example 326 * var p1 = board.create('point', [0.0, 4.0]); 327 * var p2 = board.create('point', [6.0, 1.0]); 328 * var l1 = board.create('line', [p1, p2]); 329 * var p3 = board.create('point', [3.0, 3.0]); 330 * 331 * var pp1 = board.create('perpendicularpoint', [p3, l1]); 332 * </pre><div class="jxgbox" id="JXGded148c9-3536-44c0-ab81-1bb8fa48f3f4" style="width: 400px; height: 400px;"></div> 333 * <script type="text/javascript"> 334 * var ppex1_board = JXG.JSXGraph.initBoard('JXGded148c9-3536-44c0-ab81-1bb8fa48f3f4', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 335 * var ppex1_p1 = ppex1_board.create('point', [0.0, 4.0]); 336 * var ppex1_p2 = ppex1_board.create('point', [6.0, 1.0]); 337 * var ppex1_l1 = ppex1_board.create('line', [ppex1_p1, ppex1_p2]); 338 * var ppex1_p3 = ppex1_board.create('point', [3.0, 3.0]); 339 * var ppex1_pp1 = ppex1_board.create('perpendicularpoint', [ppex1_p3, ppex1_l1]); 340 * </script><pre> 341 */ 342 JXG.createPerpendicularPoint = function (board, parents, attributes) { 343 var l, p, t; 344 345 parents[0] = board.select(parents[0]); 346 parents[1] = board.select(parents[1]); 347 if ( 348 Type.isPointType(board, parents[0]) && 349 parents[1].elementClass === Const.OBJECT_CLASS_LINE 350 ) { 351 p = Type.providePoints(board, [parents[0]], attributes, "point")[0]; 352 l = parents[1]; 353 } else if ( 354 Type.isPointType(board, parents[1]) && 355 parents[0].elementClass === Const.OBJECT_CLASS_LINE 356 ) { 357 p = Type.providePoints(board, [parents[1]], attributes, "point")[0]; 358 l = parents[0]; 359 } else { 360 throw new Error( 361 "JSXGraph: Can't create perpendicular point with parent types '" + 362 typeof parents[0] + 363 "' and '" + 364 typeof parents[1] + 365 "'." + 366 "\nPossible parent types: [point,line]" 367 ); 368 } 369 370 /** 371 * @class 372 * @ignore 373 */ 374 t = board.create( 375 "point", 376 [ 377 function () { 378 return Geometry.perpendicular(l, p, board)[0]; 379 } 380 ], 381 attributes 382 ); 383 384 if (Type.exists(p._is_new)) { 385 t.addChild(p); 386 delete p._is_new; 387 } else { 388 p.addChild(t); 389 } 390 l.addChild(t); 391 392 t.elType = "perpendicularpoint"; 393 t.setParents([p.id, l.id]); 394 395 t.update(); 396 397 /** 398 * Used to generate a polynomial for the perpendicular point 399 * @name PerpendicularPoint#generatePolynomial 400 * @returns {Array} An array containing the generated polynomial. 401 * @private 402 * @function 403 * @ignore 404 */ 405 t.generatePolynomial = function () { 406 /* 407 * Perpendicular takes point P and line L and creates point T and line M: 408 * 409 * | M 410 * | 411 * x P (p1,p2) 412 * | 413 * | 414 * L | 415 * ----------x-------------x------------------------x-------- 416 * A (a1,a2) |T (t1,t2) B (b1,b2) 417 * | 418 * | 419 * 420 * So we have two conditions: 421 * 422 * (a) AT || TB (collinearity condition) 423 * (b) PT _|_ AB (orthogonality condition) 424 * 425 * a2-t2 t2-b2 426 * ------- = ------- (1) 427 * a1-t1 t1-b1 428 * 429 * p2-t2 a1-b1 430 * ------- = - ------- (2) 431 * p1-t1 a2-b2 432 * 433 * Multiplying (1) and (2) with denominators and simplifying gives 434 * 435 * a2t1 - a2b1 + t2b1 - a1t2 + a1b2 - t1b2 = 0 (1') 436 * 437 * p2a2 - p2b2 - t2a2 + t2b2 + p1a1 - p1b1 - t1a1 + t1b1 = 0 (2') 438 * 439 */ 440 var a1 = l.point1.symbolic.x, 441 a2 = l.point1.symbolic.y, 442 b1 = l.point2.symbolic.x, 443 b2 = l.point2.symbolic.y, 444 p1 = p.symbolic.x, 445 p2 = p.symbolic.y, 446 t1 = t.symbolic.x, 447 t2 = t.symbolic.y, 448 poly1 = "(" + a2 + ")*(" + t1 + ")-(" + a2 + ")*(" + b1 + ")+(" + t2 + ")*(" + b1 + ")-(" + a1 + ")*(" + t2 + ")+(" + a1 + ")*(" + b2 + ")-(" + t1 + 449 ")*(" + b2 + ")", 450 poly2 = "(" + p2 + ")*(" + a2 + ")-(" + p2 + ")*(" + b2 + ")-(" + t2 + ")*(" + a2 + ")+(" + t2 + ")*(" + b2 + ")+(" + p1 + ")*(" + a1 + ")-(" + p1 + 451 ")*(" + b1 + ")-(" + t1 + ")*(" + a1 + ")+(" + t1 + ")*(" + b1 + ")"; 452 453 return [poly1, poly2]; 454 }; 455 456 return t; 457 }; 458 459 /** 460 * @class This element is used to provide a constructor for a perpendicular segment. 461 * @pseudo 462 * @description A perpendicular is a composition of two elements: a line segment and a point. The line segment is orthogonal 463 * to a given line and contains a given point and meets the given line in the perpendicular point. 464 * @name PerpendicularSegment 465 * @constructor 466 * @type JXG.Line 467 * @augments Segment 468 * @returns An array containing two elements: A {@link JXG.Line} object in the first component and a 469 * {@link JXG.Point} element in the second component. The line segment is orthogonal to the given line and meets it 470 * in the returned point. 471 * @throws {Error} If the elements cannot be constructed with the given parent objects an exception is thrown. 472 * @param {JXG.Line_JXG.Point} l,p The perpendicular line will be orthogonal to l and 473 * will contain p. The perpendicular point is the intersection point of the two lines. 474 * @example 475 * // Create a perpendicular 476 * var p1 = board.create('point', [0.0, 2.0]); 477 * var p2 = board.create('point', [2.0, 1.0]); 478 * var l1 = board.create('line', [p1, p2]); 479 * 480 * var p3 = board.create('point', [3.0, 3.0]); 481 * var perp1 = board.create('perpendicularsegment', [l1, p3]); 482 * </pre><div class="jxgbox" id="JXG037a6eb2-781d-4b71-b286-763619a63f22" style="width: 400px; height: 400px;"></div> 483 * <script type="text/javascript"> 484 * var pex1_board = JXG.JSXGraph.initBoard('JXG037a6eb2-781d-4b71-b286-763619a63f22', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 485 * var pex1_p1 = pex1_board.create('point', [0.0, 2.0]); 486 * var pex1_p2 = pex1_board.create('point', [2.0, 1.0]); 487 * var pex1_l1 = pex1_board.create('line', [pex1_p1, pex1_p2]); 488 * var pex1_p3 = pex1_board.create('point', [3.0, 3.0]); 489 * var pex1_perp1 = pex1_board.create('perpendicularsegment', [pex1_l1, pex1_p3]); 490 * </script><pre> 491 */ 492 JXG.createPerpendicularSegment = function (board, parents, attributes) { 493 var p, l, pd, t, attr; 494 495 parents[0] = board.select(parents[0]); 496 parents[1] = board.select(parents[1]); 497 if ( 498 Type.isPointType(board, parents[0]) && 499 parents[1].elementClass === Const.OBJECT_CLASS_LINE 500 ) { 501 l = parents[1]; 502 p = Type.providePoints(board, [parents[0]], attributes, "point")[0]; 503 } else if ( 504 Type.isPointType(board, parents[1]) && 505 parents[0].elementClass === Const.OBJECT_CLASS_LINE 506 ) { 507 l = parents[0]; 508 p = Type.providePoints(board, [parents[1]], attributes, "point")[0]; 509 } else { 510 throw new Error( 511 "JSXGraph: Can't create perpendicular with parent types '" + 512 typeof parents[0] + 513 "' and '" + 514 typeof parents[1] + 515 "'." + 516 "\nPossible parent types: [line,point]" 517 ); 518 } 519 attr = Type.copyAttributes(attributes, board.options, "perpendicularsegment", "point"); 520 t = JXG.createPerpendicularPoint(board, [l, p], attr); 521 t.dump = false; 522 523 if (!Type.exists(attributes.layer)) { 524 attributes.layer = board.options.layer.line; 525 } 526 527 attr = Type.copyAttributes(attributes, board.options, "perpendicularsegment"); 528 pd = JXG.createLine( 529 board, 530 [ 531 function () { 532 return Geometry.perpendicular(l, p, board)[1] ? [t, p] : [p, t]; 533 } 534 ], 535 attr 536 ); 537 538 /** 539 * Helper point 540 * @memberOf PerpendicularSegment.prototype 541 * @type PerpendicularPoint 542 * @name point 543 */ 544 pd.point = t; 545 546 if (Type.exists(p._is_new)) { 547 pd.addChild(p); 548 delete p._is_new; 549 } else { 550 p.addChild(pd); 551 } 552 l.addChild(pd); 553 554 pd.elType = "perpendicularsegment"; 555 pd.setParents([p.id, l.id]); 556 pd.subs = { 557 point: t 558 }; 559 pd.inherits.push(t); 560 561 return pd; 562 }; 563 564 /** 565 * @class The midpoint element constructs a point in the middle of two given points. 566 * @pseudo 567 * @description A midpoint is given by two points. It is collinear to the given points and the distance 568 * is the same to each of the given points, i.e. it is in the middle of the given points. 569 * @constructor 570 * @name Midpoint 571 * @type JXG.Point 572 * @augments JXG.Point 573 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 574 * @param {JXG.Point_JXG.Point} p1,p2 The constructed point will be in the middle of p1 and p2. 575 * @param {JXG.Line} l The midpoint will be in the middle of {@link JXG.Line#point1} and {@link JXG.Line#point2} of 576 * the given line l. 577 * @example 578 * // Create base elements: 2 points and 1 line 579 * var p1 = board.create('point', [0.0, 2.0]); 580 * var p2 = board.create('point', [2.0, 1.0]); 581 * var l1 = board.create('segment', [[0.0, 3.0], [3.0, 3.0]]); 582 * 583 * var mp1 = board.create('midpoint', [p1, p2]); 584 * var mp2 = board.create('midpoint', [l1]); 585 * </pre><div class="jxgbox" id="JXG7927ef86-24ae-40cc-afb0-91ff61dd0de7" style="width: 400px; height: 400px;"></div> 586 * <script type="text/javascript"> 587 * var mpex1_board = JXG.JSXGraph.initBoard('JXG7927ef86-24ae-40cc-afb0-91ff61dd0de7', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 588 * var mpex1_p1 = mpex1_board.create('point', [0.0, 2.0]); 589 * var mpex1_p2 = mpex1_board.create('point', [2.0, 1.0]); 590 * var mpex1_l1 = mpex1_board.create('segment', [[0.0, 3.0], [3.0, 3.0]]); 591 * var mpex1_mp1 = mpex1_board.create('midpoint', [mpex1_p1, mpex1_p2]); 592 * var mpex1_mp2 = mpex1_board.create('midpoint', [mpex1_l1]); 593 * </script><pre> 594 */ 595 JXG.createMidpoint = function (board, parents, attributes) { 596 var a, b, t, i, attr; 597 598 for (i = 0; i < parents.length; ++i) { 599 parents[i] = board.select(parents[i]); 600 } 601 if ( 602 parents.length === 2 && 603 Type.isPointType(board, parents[0]) && 604 Type.isPointType(board, parents[1]) 605 ) { 606 parents = Type.providePoints(board, parents, attributes, "point"); 607 a = parents[0]; 608 b = parents[1]; 609 } else if (parents.length === 1 && parents[0].elementClass === Const.OBJECT_CLASS_LINE) { 610 a = parents[0].point1; 611 b = parents[0].point2; 612 } else { 613 throw new Error( 614 "JSXGraph: Can't create midpoint." + 615 "\nPossible parent types: [point,point], [line]" 616 ); 617 } 618 619 attr = Type.copyAttributes(attributes, board.options, "midpoint"); 620 /** 621 * @type JXG.Element 622 * @ignore 623 */ 624 t = board.create( 625 "point", 626 [ 627 function () { 628 var x = a.coords.usrCoords[1] + b.coords.usrCoords[1]; 629 if ( 630 isNaN(x) || 631 Math.abs(a.coords.usrCoords[0]) < Mat.eps || 632 Math.abs(b.coords.usrCoords[0]) < Mat.eps 633 ) { 634 return NaN; 635 } 636 637 return x * 0.5; 638 }, 639 function () { 640 var y = a.coords.usrCoords[2] + b.coords.usrCoords[2]; 641 if ( 642 isNaN(y) || 643 Math.abs(a.coords.usrCoords[0]) < Mat.eps || 644 Math.abs(b.coords.usrCoords[0]) < Mat.eps 645 ) { 646 return NaN; 647 } 648 649 return y * 0.5; 650 } 651 ], 652 attr 653 ); 654 if (Type.exists(a._is_new)) { 655 t.addChild(a); 656 delete a._is_new; 657 } else { 658 a.addChild(t); 659 } 660 if (Type.exists(b._is_new)) { 661 t.addChild(b); 662 delete b._is_new; 663 } else { 664 b.addChild(t); 665 } 666 667 t.elType = "midpoint"; 668 t.setParents([a.id, b.id]); 669 670 t.prepareUpdate().update(); 671 672 /** 673 * Used to generate a polynomial for the midpoint. 674 * @name Midpoint#generatePolynomial 675 * @returns {Array} An array containing the generated polynomial. 676 * @private 677 * @function 678 * @ignore 679 */ 680 t.generatePolynomial = function () { 681 /* 682 * Midpoint takes two point A and B or line L (with points P and Q) and creates point T: 683 * 684 * L (not necessarily) 685 * ----------x------------------x------------------x-------- 686 * A (a1,a2) T (t1,t2) B (b1,b2) 687 * 688 * So we have two conditions: 689 * 690 * (a) AT || TB (collinearity condition) 691 * (b) [AT] == [TB] (equidistant condition) 692 * 693 * a2-t2 t2-b2 694 * ------- = ------- (1) 695 * a1-t1 t1-b1 696 * 697 * (a1 - t1)^2 + (a2 - t2)^2 = (b1 - t1)^2 + (b2 - t2)^2 (2) 698 * 699 * 700 * Multiplying (1) with denominators and simplifying (1) and (2) gives 701 * 702 * a2t1 - a2b1 + t2b1 - a1t2 + a1b2 - t1b2 = 0 (1') 703 * 704 * a1^2 - 2a1t1 + a2^2 - 2a2t2 - b1^2 + 2b1t1 - b2^2 + 2b2t2 = 0 (2') 705 * 706 */ 707 var a1 = a.symbolic.x, 708 a2 = a.symbolic.y, 709 b1 = b.symbolic.x, 710 b2 = b.symbolic.y, 711 t1 = t.symbolic.x, 712 t2 = t.symbolic.y, 713 poly1 = "(" + a2 + ")*(" + t1 + ")-(" + a2 + ")*(" + b1 + ")+(" + t2 + ")*(" + b1 + ")-(" + a1 + ")*(" + t2 + ")+(" + a1 + ")*(" + b2 + 714 ")-(" + t1 + ")*(" + b2 + ")", 715 poly2 = "(" + a1 + ")^2 - 2*(" + a1 + ")*(" + t1 + ")+(" + a2 + ")^2-2*(" + a2 + ")*(" + t2 + ")-(" + b1 + ")^2+2*(" + b1 + ")*(" + t1 + 716 ")-(" + b2 + ")^2+2*(" + b2 + ")*(" + t2 + ")"; 717 718 return [poly1, poly2]; 719 }; 720 721 return t; 722 }; 723 724 /** 725 * @class This element is used to construct a parallel point. 726 * @pseudo 727 * @description A parallel point is given by three points. Taking the Euclidean vector from the first to the 728 * second point, the parallel point is determined by adding that vector to the third point. 729 * The line determined by the first two points is parallel to the line determined by the third point and the constructed point. 730 * @constructor 731 * @name Parallelpoint 732 * @type JXG.Point 733 * @augments JXG.Point 734 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 735 * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 Taking the Euclidean vector <tt>v=p2-p1</tt> the parallel point is determined by 736 * <tt>p4 = p3+v</tt> 737 * @param {JXG.Line_JXG.Point} l,p The resulting point will together with p specify a line which is parallel to l. 738 * @example 739 * var p1 = board.create('point', [0.0, 2.0]); 740 * var p2 = board.create('point', [2.0, 1.0]); 741 * var p3 = board.create('point', [3.0, 3.0]); 742 * 743 * var pp1 = board.create('parallelpoint', [p1, p2, p3]); 744 * </pre><div class="jxgbox" id="JXG488c4be9-274f-40f0-a469-c5f70abe1f0e" style="width: 400px; height: 400px;"></div> 745 * <script type="text/javascript"> 746 * var ppex1_board = JXG.JSXGraph.initBoard('JXG488c4be9-274f-40f0-a469-c5f70abe1f0e', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 747 * var ppex1_p1 = ppex1_board.create('point', [0.0, 2.0]); 748 * var ppex1_p2 = ppex1_board.create('point', [2.0, 1.0]); 749 * var ppex1_p3 = ppex1_board.create('point', [3.0, 3.0]); 750 * var ppex1_pp1 = ppex1_board.create('parallelpoint', [ppex1_p1, ppex1_p2, ppex1_p3]); 751 * </script><pre> 752 */ 753 JXG.createParallelPoint = function (board, parents, attributes) { 754 var a, b, c, p, i, attr; 755 756 for (i = 0; i < parents.length; ++i) { 757 parents[i] = board.select(parents[i]); 758 } 759 if ( 760 parents.length === 3 && 761 Type.isPointType(board, parents[0]) && 762 Type.isPointType(board, parents[1]) && 763 Type.isPointType(board, parents[2]) 764 ) { 765 parents = Type.providePoints(board, parents, attributes, "point"); 766 a = parents[0]; 767 b = parents[1]; 768 c = parents[2]; 769 } else if ( 770 Type.isPointType(board, parents[0]) && 771 parents[1].elementClass === Const.OBJECT_CLASS_LINE 772 ) { 773 c = Type.providePoints(board, [parents[0]], attributes, "point")[0]; 774 a = parents[1].point1; 775 b = parents[1].point2; 776 } else if ( 777 Type.isPointType(board, parents[1]) && 778 parents[0].elementClass === Const.OBJECT_CLASS_LINE 779 ) { 780 c = Type.providePoints(board, [parents[1]], attributes, "point")[0]; 781 a = parents[0].point1; 782 b = parents[0].point2; 783 } else { 784 throw new Error( 785 "JSXGraph: Can't create parallel point with parent types '" + 786 typeof parents[0] + 787 "', '" + 788 typeof parents[1] + 789 "' and '" + 790 typeof parents[2] + 791 "'." + 792 "\nPossible parent types: [line,point], [point,point,point]" 793 ); 794 } 795 796 attr = Type.copyAttributes(attributes, board.options, 'parallelpoint'); 797 /** 798 * @type {JXG.Element} 799 * @ignore 800 */ 801 p = board.create( 802 "point", 803 [ 804 function () { 805 return c.coords.usrCoords[1] + b.coords.usrCoords[1] - a.coords.usrCoords[1]; 806 }, 807 function () { 808 return c.coords.usrCoords[2] + b.coords.usrCoords[2] - a.coords.usrCoords[2]; 809 } 810 ], 811 attr 812 ); 813 814 // required for algorithms requiring dependencies between elements 815 if (Type.exists(a._is_new)) { 816 p.addChild(a); 817 delete a._is_new; 818 } else { 819 a.addChild(p); 820 } 821 if (Type.exists(b._is_new)) { 822 p.addChild(b); 823 delete b._is_new; 824 } else { 825 b.addChild(p); 826 } 827 if (Type.exists(c._is_new)) { 828 p.addChild(c); 829 delete c._is_new; 830 } else { 831 c.addChild(p); 832 } 833 834 p.elType = "parallelpoint"; 835 p.setParents([a.id, b.id, c.id]); 836 837 // required to set the coordinates because functions are considered as constraints. hence, the coordinates get set first after an update. 838 // can be removed if the above issue is resolved. 839 p.prepareUpdate().update(); 840 841 /** 842 * @function 843 * @ignore 844 */ 845 p.generatePolynomial = function () { 846 /* 847 * Parallelpoint takes three points A, B and C or line L (with points B and C) and creates point T: 848 * 849 * 850 * C (c1,c2) T (t1,t2) 851 * x x 852 * / / 853 * / / 854 * / / 855 * / / 856 * / / 857 * / / 858 * / / 859 * / / 860 * L (opt) / / 861 * ----------x-------------------------------------x-------- 862 * A (a1,a2) B (b1,b2) 863 * 864 * So we have two conditions: 865 * 866 * (a) CT || AB (collinearity condition I) 867 * (b) BT || AC (collinearity condition II) 868 * 869 * The corresponding equations are 870 * 871 * (b2 - a2)(t1 - c1) - (t2 - c2)(b1 - a1) = 0 (1) 872 * (t2 - b2)(a1 - c1) - (t1 - b1)(a2 - c2) = 0 (2) 873 * 874 * Simplifying (1) and (2) gives 875 * 876 * b2t1 - b2c1 - a2t1 + a2c1 - t2b1 + t2a1 + c2b1 - c2a1 = 0 (1') 877 * t2a1 - t2c1 - b2a1 + b2c1 - t1a2 + t1c2 + b1a2 - b1c2 = 0 (2') 878 * 879 */ 880 var a1 = a.symbolic.x, 881 a2 = a.symbolic.y, 882 b1 = b.symbolic.x, 883 b2 = b.symbolic.y, 884 c1 = c.symbolic.x, 885 c2 = c.symbolic.y, 886 t1 = p.symbolic.x, 887 t2 = p.symbolic.y, 888 poly1 = "(" + b2 + ")*(" + t1 + ")-(" + b2 + ")*(" + c1 + ")-(" + a2 + ")*(" + t1 + ")+(" + a2 + ")*(" + c1 + ")-(" + t2 + ")*(" + b1 + ")+(" + t2 + ")*(" + 889 a1 + ")+(" + c2 + ")*(" + b1 + ")-(" + c2 + ")*(" + a1 + ")", 890 poly2 = "(" + t2 + ")*(" + a1 + ")-(" + t2 + ")*(" + c1 + ")-(" + b2 + ")*(" + a1 + ")+(" + b2 + ")*(" + c1 + ")-(" + t1 + ")*(" + a2 + ")+(" + t1 + ")*(" + 891 c2 + ")+(" + b1 + ")*(" + a2 + ")-(" + b1 + ")*(" + c2 + ")"; 892 893 return [poly1, poly2]; 894 }; 895 896 return p; 897 }; 898 899 /** 900 * @class A parallel is a line through a given point with the same slope as a given line or 901 * the line through two given point. 902 * <p> 903 * If original line is given as a JSXGraph line object, the resulting parallel line will be defined by the given point and an 904 * infinitely far away point (an ideal point). That means, the line can not be shortened to a segment. 905 * <p> 906 * If the original line is given as two points, the resulting parallel line can be shortened to a a segment. 907 * @pseudo 908 * @name Parallel 909 * @augments Line 910 * @constructor 911 * @type JXG.Line 912 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 913 * @param {JXG.Line_JXG.Point} l,p The constructed line contains p and has the same slope as l. Alternative parameters are p1, p2, p: The 914 * constructed line contains p and has the same slope as the line through p1 and p2. 915 * @example 916 * // Create a parallel 917 * var p1 = board.create('point', [0.0, 2.0]); 918 * var p2 = board.create('point', [2.0, 1.0]); 919 * var l1 = board.create('line', [p1, p2]); 920 * 921 * var p3 = board.create('point', [3.0, 3.0]); 922 * var pl1 = board.create('parallel', [l1, p3]); 923 * </pre><div class="jxgbox" id="JXG24e54f9e-5c4e-4afb-9228-0ef27a59d627" style="width: 400px; height: 400px;"></div> 924 * <script type="text/javascript"> 925 * var plex1_board = JXG.JSXGraph.initBoard('JXG24e54f9e-5c4e-4afb-9228-0ef27a59d627', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 926 * var plex1_p1 = plex1_board.create('point', [0.0, 2.0]); 927 * var plex1_p2 = plex1_board.create('point', [2.0, 1.0]); 928 * var plex1_l1 = plex1_board.create('line', [plex1_p1, plex1_p2]); 929 * var plex1_p3 = plex1_board.create('point', [3.0, 3.0]); 930 * var plex1_pl1 = plex1_board.create('parallel', [plex1_l1, plex1_p3]); 931 * </script><pre> 932 * @example 933 * var p1, p2, p3, l1, pl1; 934 * 935 * p1 = board.create('point', [0.0, 2.0]); 936 * p2 = board.create('point', [2.0, 1.0]); 937 * l1 = board.create('line', [p1, p2]); 938 * 939 * p3 = board.create('point', [1.0, 3.0]); 940 * pl1 = board.create('parallel', [p1, p2, p3], {straightFirst: false, straightLast: false}); 941 * 942 * </pre><div id="JXGd643305d-20c3-4a88-91f9-8d0c4448594f" class="jxgbox" style="width: 300px; height: 300px;"></div> 943 * <script type="text/javascript"> 944 * (function() { 945 * var board = JXG.JSXGraph.initBoard('JXGd643305d-20c3-4a88-91f9-8d0c4448594f', 946 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 947 * var p1, p2, p3, l1, pl1; 948 * 949 * p1 = board.create('point', [0.0, 2.0]); 950 * p2 = board.create('point', [2.0, 1.0]); 951 * l1 = board.create('line', [p1, p2]); 952 * 953 * p3 = board.create('point', [1.0, 3.0]); 954 * pl1 = board.create('parallel', [p1, p2, p3], {straightFirst: false, straightLast: false}); 955 * 956 * })(); 957 * 958 * </script><pre> 959 * 960 */ 961 JXG.createParallel = function (board, parents, attributes) { 962 var p, 963 pp, 964 pl, 965 li, 966 i, 967 attr, 968 ty = 1; 969 970 for (i = 0; i < parents.length; ++i) { 971 parents[i] = board.select(parents[i]); 972 } 973 p = null; 974 if (parents.length === 3) { 975 // Line / segment through point parents[2] which is parallel to line through parents[0] and parents[1] 976 parents = Type.providePoints(board, parents, attributes, "point"); 977 p = parents[2]; 978 ty = 0; 979 } else if (Type.isPointType(board, parents[0])) { 980 // Parallel to line parents[1] through point parents[0] 981 p = Type.providePoints(board, [parents[0]], attributes, "point")[0]; 982 /** @ignore */ 983 li = function () { 984 return parents[1].stdform; 985 }; 986 } else if (Type.isPointType(board, parents[1])) { 987 // Parallel to line parents[0] through point parents[1] 988 p = Type.providePoints(board, [parents[1]], attributes, "point")[0]; 989 /** @ignore */ 990 li = function () { 991 return parents[0].stdform; 992 }; 993 } 994 995 if (!Type.exists(attributes.layer)) { 996 attributes.layer = board.options.layer.line; 997 } 998 999 attr = Type.copyAttributes(attributes, board.options, "parallel", "point"); 1000 if (ty === 1) { 1001 // Line is given by line element. The parallel line is 1002 // constructed as line through an ideal point. 1003 pp = board.create( 1004 "point", 1005 [ 1006 function () { 1007 return Mat.crossProduct([1, 0, 0], li()); 1008 } 1009 ], 1010 attr 1011 ); 1012 } else { 1013 // Line is given by two points. The parallel line is 1014 // constructed as line through two finite point. 1015 pp = board.create("parallelpoint", parents, attr); 1016 } 1017 pp.isDraggable = true; 1018 1019 attr = Type.copyAttributes(attributes, board.options, "parallel"); 1020 // line creator also calls addChild 1021 pl = board.create("line", [p, pp], attr); 1022 1023 pl.elType = "parallel"; 1024 pl.subs = { 1025 point: pp 1026 }; 1027 1028 pl.inherits.push(pp); 1029 pl.setParents([parents[0].id, parents[1].id]); 1030 if (parents.length === 3) { 1031 pl.addParents(parents[2].id); 1032 } 1033 1034 // p.addChild(pl); 1035 1036 /** 1037 * Helper point used to create the parallel line. This point lies on the line at infinity, hence it's not visible, 1038 * not even with visible set to <tt>true</tt>. Creating another line through this point would make that other line 1039 * parallel to the create parallel. 1040 * @memberOf Parallel.prototype 1041 * @name point 1042 * @type JXG.Point 1043 */ 1044 pl.point = pp; 1045 1046 return pl; 1047 }; 1048 1049 /** 1050 * @class An arrow parallel is a segment with an arrow attached which is parallel through a given segment, given by its defining two points, 1051 * through a given point. 1052 * <p> 1053 * @pseudo 1054 * @constructor 1055 * @name Arrowparallel 1056 * @type Parallel 1057 * @augments Parallel 1058 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 1059 * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 The constructed arrow contains p3 and has the same slope as the line through p1 and p2. 1060 * @example 1061 * // Create a parallel 1062 * var p1 = board.create('point', [0.0, 2.0]); 1063 * var p2 = board.create('point', [2.0, 1.0]); 1064 * var l1 = board.create('segment', [p1, p2]); 1065 * 1066 * var p3 = board.create('point', [3.0, 3.0]); 1067 * var pl1 = board.create('arrowparallel', [p1, p2, p3]); 1068 * </pre><div class="jxgbox" id="JXGeeacdf99-036f-4e83-aeb6-f7388423e369" style="width: 400px; height: 400px;"></div> 1069 * <script type="text/javascript"> 1070 * (function () { 1071 * var plex1_board = JXG.JSXGraph.initBoard('JXGeeacdf99-036f-4e83-aeb6-f7388423e369', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 1072 * var plex1_p1 = plex1_board.create('point', [0.0, 2.0]); 1073 * var plex1_p2 = plex1_board.create('point', [2.0, 1.0]); 1074 * var plex1_l1 = plex1_board.create('segment', [plex1_p1, plex1_p2]); 1075 * var plex1_p3 = plex1_board.create('point', [3.0, 3.0]); 1076 * var plex1_pl1 = plex1_board.create('arrowparallel', [plex1_p1, plex1_p2, plex1_p3]); 1077 * })(); 1078 * </script><pre> 1079 */ 1080 JXG.createArrowParallel = function (board, parents, attributes) { 1081 var p, attr; 1082 1083 /* parallel arrow point polynomials are done in createParallelPoint */ 1084 try { 1085 attr = Type.copyAttributes(attributes, board.options, 'arrowparallel'); 1086 attr.firstArrow = false; 1087 attr.lastArrow = true; 1088 p = JXG.createParallel(board, parents, attr).setAttribute({ 1089 straightFirst: false, 1090 straightLast: false 1091 }); 1092 p.elType = "arrowparallel"; 1093 1094 // parents are set in createParallel 1095 1096 return p; 1097 } catch (e) { 1098 throw new Error( 1099 "JSXGraph: Can't create arrowparallel with parent types '" + 1100 typeof parents[0] + 1101 "' and '" + 1102 typeof parents[1] + 1103 "'." + 1104 "\nPossible parent types: [line,point], [point,point,point]" 1105 ); 1106 } 1107 }; 1108 1109 /** 1110 * @class Constructs a normal. 1111 * @pseudo 1112 * @description A normal is a line through a given point on a element of type line, circle, curve, or turtle and orthogonal to that object. 1113 * @constructor 1114 * @name Normal 1115 * @type JXG.Line 1116 * @augments JXG.Line 1117 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 1118 * @param {JXG.Line,JXG.Circle,JXG.Curve,JXG.Turtle_JXG.Point} o,p The constructed line contains p which lies on the object and is orthogonal 1119 * to the tangent to the object in the given point. 1120 * @param {Glider} p Works like above, however the object is given by {@link JXG.CoordsElement#slideObject}. 1121 * @example 1122 * // Create a normal to a circle. 1123 * var p1 = board.create('point', [2.0, 2.0]); 1124 * var p2 = board.create('point', [3.0, 2.0]); 1125 * var c1 = board.create('circle', [p1, p2]); 1126 * 1127 * var norm1 = board.create('normal', [c1, p2]); 1128 * </pre><div class="jxgbox" id="JXG4154753d-3d29-40fb-a860-0b08aa4f3743" style="width: 400px; height: 400px;"></div> 1129 * <script type="text/javascript"> 1130 * var nlex1_board = JXG.JSXGraph.initBoard('JXG4154753d-3d29-40fb-a860-0b08aa4f3743', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 1131 * var nlex1_p1 = nlex1_board.create('point', [2.0, 2.0]); 1132 * var nlex1_p2 = nlex1_board.create('point', [3.0, 2.0]); 1133 * var nlex1_c1 = nlex1_board.create('circle', [nlex1_p1, nlex1_p2]); 1134 * 1135 * // var nlex1_p3 = nlex1_board.create('point', [1.0, 2.0]); 1136 * var nlex1_norm1 = nlex1_board.create('normal', [nlex1_c1, nlex1_p2]); 1137 * </script><pre> 1138 */ 1139 JXG.createNormal = function (board, parents, attributes) { 1140 var p, c, l, i, g, f, attr, pp, attrp; 1141 1142 for (i = 0; i < parents.length; ++i) { 1143 parents[i] = board.select(parents[i]); 1144 } 1145 // One arguments: glider on line, circle or curve 1146 if (parents.length === 1) { 1147 p = parents[0]; 1148 c = p.slideObject; 1149 // Two arguments: (point,line), (point,circle), (line,point) or (circle,point) 1150 } else if (parents.length === 2) { 1151 if (Type.isPointType(board, parents[0])) { 1152 p = Type.providePoints(board, [parents[0]], attributes, "point")[0]; 1153 c = parents[1]; 1154 } else if (Type.isPointType(board, parents[1])) { 1155 c = parents[0]; 1156 p = Type.providePoints(board, [parents[1]], attributes, "point")[0]; 1157 } else { 1158 throw new Error( 1159 "JSXGraph: Can't create normal with parent types '" + 1160 typeof parents[0] + 1161 "' and '" + 1162 typeof parents[1] + 1163 "'." + 1164 "\nPossible parent types: [point,line], [point,circle], [glider]" 1165 ); 1166 } 1167 } else { 1168 throw new Error( 1169 "JSXGraph: Can't create normal with parent types '" + 1170 typeof parents[0] + 1171 "' and '" + 1172 typeof parents[1] + 1173 "'." + 1174 "\nPossible parent types: [point,line], [point,circle], [glider]" 1175 ); 1176 } 1177 1178 attr = Type.copyAttributes(attributes, board.options, "normal"); 1179 if (c.elementClass === Const.OBJECT_CLASS_LINE) { 1180 // Private point 1181 attrp = Type.copyAttributes(attributes, board.options, "normal", "point"); 1182 pp = board.create( 1183 "point", 1184 [ 1185 function () { 1186 var p = Mat.crossProduct([1, 0, 0], c.stdform); 1187 return [p[0], -p[2], p[1]]; 1188 } 1189 ], 1190 attrp 1191 ); 1192 pp.isDraggable = true; 1193 1194 l = board.create("line", [p, pp], attr); 1195 1196 /** 1197 * A helper point used to create a normal to a {@link JXG.Line} object. For normals to circles or curves this 1198 * element is <tt>undefined</tt>. 1199 * @type JXG.Point 1200 * @name point 1201 * @memberOf Normal.prototype 1202 */ 1203 l.point = pp; 1204 l.subs = { 1205 point: pp 1206 }; 1207 l.inherits.push(pp); 1208 } else if (c.elementClass === Const.OBJECT_CLASS_CIRCLE) { 1209 l = board.create("line", [c.midpoint, p], attr); 1210 } else if (c.elementClass === Const.OBJECT_CLASS_CURVE) { 1211 if (Type.evaluate(c.visProp.curvetype) !== "plot") { 1212 g = c.X; 1213 f = c.Y; 1214 l = board.create( 1215 "line", 1216 [ 1217 function () { 1218 return ( 1219 -p.X() * Numerics.D(g)(p.position) - 1220 p.Y() * Numerics.D(f)(p.position) 1221 ); 1222 }, 1223 function () { 1224 return Numerics.D(g)(p.position); 1225 }, 1226 function () { 1227 return Numerics.D(f)(p.position); 1228 } 1229 ], 1230 attr 1231 ); 1232 } else { 1233 // curveType 'plot' 1234 l = board.create( 1235 "line", 1236 [ 1237 function () { 1238 var i = Math.floor(p.position), 1239 lbda = p.position - i, 1240 p1, 1241 p2, 1242 t, 1243 A, 1244 B, 1245 C, 1246 D, 1247 dx, 1248 dy, 1249 d; 1250 1251 if (c.bezierdegree === 1) { 1252 if (i === c.numberPoints - 1) { 1253 i -= 1; 1254 lbda = 1; 1255 } 1256 } else if (c.bezierDegree === 3) { 1257 // i is start of the Bezier segment 1258 // t is the position in the Bezier segment 1259 i = Math.floor((p.position * (c.numberPoints - 1)) / 3) * 3; 1260 t = (p.position * (c.numberPoints - 1) - i) / 3; 1261 if (i >= c.numberPoints - 1) { 1262 i = c.numberPoints - 4; 1263 t = 1; 1264 } 1265 } else { 1266 return 0; 1267 } 1268 1269 if (i < 0) { 1270 return 1; 1271 } 1272 1273 if (c.bezierDegree === 1) { 1274 return ( 1275 (c.Y(i) + lbda * (c.Y(i + 1) - c.Y(i))) * 1276 (c.Y(i) - c.Y(i + 1)) - 1277 (c.X(i) + lbda * (c.X(i + 1) - c.X(i))) * (c.X(i + 1) - c.X(i)) 1278 ); 1279 } else { 1280 A = c.points[i].usrCoords; 1281 B = c.points[i + 1].usrCoords; 1282 C = c.points[i + 2].usrCoords; 1283 D = c.points[i + 3].usrCoords; 1284 dx = 1285 (1 - t) * (1 - t) * (B[1] - A[1]) + 1286 2 * (1 - t) * t * (C[1] - B[1]) + 1287 t * t * (D[1] - C[1]); 1288 dy = 1289 (1 - t) * (1 - t) * (B[2] - A[2]) + 1290 2 * (1 - t) * t * (C[2] - B[2]) + 1291 t * t * (D[2] - C[2]); 1292 d = Mat.hypot(dx, dy); 1293 dx /= d; 1294 dy /= d; 1295 p1 = p.coords.usrCoords; 1296 p2 = [1, p1[1] - dy, p1[2] + dx]; 1297 return p1[2] * p2[1] - p1[1] * p2[2]; 1298 } 1299 }, 1300 function () { 1301 var i = Math.floor(p.position), 1302 p1, 1303 p2, 1304 t, 1305 A, 1306 B, 1307 C, 1308 D, 1309 dx, 1310 dy, 1311 d; 1312 1313 if (c.bezierdegree === 1) { 1314 if (i === c.numberPoints - 1) { 1315 i -= 1; 1316 } 1317 } else if (c.bezierDegree === 3) { 1318 // i is start of the Bezier segment 1319 // t is the position in the Bezier segment 1320 i = Math.floor((p.position * (c.numberPoints - 1)) / 3) * 3; 1321 t = (p.position * (c.numberPoints - 1) - i) / 3; 1322 if (i >= c.numberPoints - 1) { 1323 i = c.numberPoints - 4; 1324 t = 1; 1325 } 1326 } else { 1327 return 0; 1328 } 1329 1330 if (i < 0) { 1331 return 0; 1332 } 1333 if (c.bezierDegree === 1) { 1334 return c.X(i + 1) - c.X(i); 1335 } else { 1336 A = c.points[i].usrCoords; 1337 B = c.points[i + 1].usrCoords; 1338 C = c.points[i + 2].usrCoords; 1339 D = c.points[i + 3].usrCoords; 1340 dx = 1341 (1 - t) * (1 - t) * (B[1] - A[1]) + 1342 2 * (1 - t) * t * (C[1] - B[1]) + 1343 t * t * (D[1] - C[1]); 1344 dy = 1345 (1 - t) * (1 - t) * (B[2] - A[2]) + 1346 2 * (1 - t) * t * (C[2] - B[2]) + 1347 t * t * (D[2] - C[2]); 1348 d = Mat.hypot(dx, dy); 1349 dx /= d; 1350 dy /= d; 1351 p1 = p.coords.usrCoords; 1352 p2 = [1, p1[1] - dy, p1[2] + dx]; 1353 return p2[2] - p1[2]; 1354 } 1355 }, 1356 function () { 1357 var i = Math.floor(p.position), 1358 p1, 1359 p2, 1360 t, 1361 A, 1362 B, 1363 C, 1364 D, 1365 dx, 1366 dy, 1367 d; 1368 1369 if (c.bezierdegree === 1) { 1370 if (i === c.numberPoints - 1) { 1371 i -= 1; 1372 } 1373 } else if (c.bezierDegree === 3) { 1374 // i is start of the Bezier segment 1375 // t is the position in the Bezier segment 1376 i = Math.floor((p.position * (c.numberPoints - 1)) / 3) * 3; 1377 t = (p.position * (c.numberPoints - 1) - i) / 3; 1378 if (i >= c.numberPoints - 1) { 1379 i = c.numberPoints - 4; 1380 t = 1; 1381 } 1382 } else { 1383 return 0; 1384 } 1385 1386 if (i < 0) { 1387 return 0; 1388 } 1389 1390 if (c.bezierDegree === 1) { 1391 return c.Y(i + 1) - c.Y(i); 1392 } else { 1393 A = c.points[i].usrCoords; 1394 B = c.points[i + 1].usrCoords; 1395 C = c.points[i + 2].usrCoords; 1396 D = c.points[i + 3].usrCoords; 1397 dx = 1398 (1 - t) * (1 - t) * (B[1] - A[1]) + 1399 2 * (1 - t) * t * (C[1] - B[1]) + 1400 t * t * (D[1] - C[1]); 1401 dy = 1402 (1 - t) * (1 - t) * (B[2] - A[2]) + 1403 2 * (1 - t) * t * (C[2] - B[2]) + 1404 t * t * (D[2] - C[2]); 1405 d = Mat.hypot(dx, dy); 1406 dx /= d; 1407 dy /= d; 1408 p1 = p.coords.usrCoords; 1409 p2 = [1, p1[1] - dy, p1[2] + dx]; 1410 return p1[1] - p2[1]; 1411 } 1412 } 1413 ], 1414 attr 1415 ); 1416 } 1417 } else if (c.type === Const.OBJECT_TYPE_TURTLE) { 1418 l = board.create( 1419 "line", 1420 [ 1421 function () { 1422 var el, 1423 j, 1424 i = Math.floor(p.position), 1425 lbda = p.position - i; 1426 1427 // run through all curves of this turtle 1428 for (j = 0; j < c.objects.length; j++) { 1429 el = c.objects[j]; 1430 1431 if (el.type === Const.OBJECT_TYPE_CURVE) { 1432 if (i < el.numberPoints) { 1433 break; 1434 } 1435 1436 i -= el.numberPoints; 1437 } 1438 } 1439 1440 if (i === el.numberPoints - 1) { 1441 i -= 1; 1442 lbda = 1; 1443 } 1444 1445 if (i < 0) { 1446 return 1; 1447 } 1448 1449 return ( 1450 (el.Y(i) + lbda * (el.Y(i + 1) - el.Y(i))) * (el.Y(i) - el.Y(i + 1)) - 1451 (el.X(i) + lbda * (el.X(i + 1) - el.X(i))) * (el.X(i + 1) - el.X(i)) 1452 ); 1453 }, 1454 function () { 1455 var el, 1456 j, 1457 i = Math.floor(p.position); 1458 1459 // run through all curves of this turtle 1460 for (j = 0; j < c.objects.length; j++) { 1461 el = c.objects[j]; 1462 if (el.type === Const.OBJECT_TYPE_CURVE) { 1463 if (i < el.numberPoints) { 1464 break; 1465 } 1466 1467 i -= el.numberPoints; 1468 } 1469 } 1470 1471 if (i === el.numberPoints - 1) { 1472 i -= 1; 1473 } 1474 1475 if (i < 0) { 1476 return 0; 1477 } 1478 1479 return el.X(i + 1) - el.X(i); 1480 }, 1481 function () { 1482 var el, 1483 j, 1484 i = Math.floor(p.position); 1485 1486 // run through all curves of this turtle 1487 for (j = 0; j < c.objects.length; j++) { 1488 el = c.objects[j]; 1489 if (el.type === Const.OBJECT_TYPE_CURVE) { 1490 if (i < el.numberPoints) { 1491 break; 1492 } 1493 1494 i -= el.numberPoints; 1495 } 1496 } 1497 1498 if (i === el.numberPoints - 1) { 1499 i -= 1; 1500 } 1501 1502 if (i < 0) { 1503 return 0; 1504 } 1505 1506 return el.Y(i + 1) - el.Y(i); 1507 } 1508 ], 1509 attr 1510 ); 1511 } else { 1512 throw new Error( 1513 "JSXGraph: Can't create normal with parent types '" + 1514 typeof parents[0] + 1515 "' and '" + 1516 typeof parents[1] + 1517 "'." + 1518 "\nPossible parent types: [point,line], [point,circle], [glider]" 1519 ); 1520 } 1521 1522 l.elType = "normal"; 1523 l.setParents(parents); 1524 1525 if (Type.exists(p._is_new)) { 1526 l.addChild(p); 1527 delete p._is_new; 1528 } else { 1529 p.addChild(l); 1530 } 1531 c.addChild(l); 1532 1533 return l; 1534 }; 1535 1536 /** 1537 * @class A bisector is a line which divides an angle into two equal angles. It is given by three points A, B, and 1538 * C and divides the angle ABC into two equal sized parts. 1539 * @pseudo 1540 * @constructor 1541 * @name Bisector 1542 * @type JXG.Line 1543 * @augments JXG.Line 1544 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 1545 * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 The angle described by <tt>p1</tt>, <tt>p2</tt> and <tt>p3</tt> will 1546 * be divided into two equal angles. 1547 * @example 1548 * var p1 = board.create('point', [6.0, 4.0]); 1549 * var p2 = board.create('point', [3.0, 2.0]); 1550 * var p3 = board.create('point', [1.0, 7.0]); 1551 * 1552 * var bi1 = board.create('bisector', [p1, p2, p3]); 1553 * </pre><div class="jxgbox" id="JXG0d58cea8-b06a-407c-b27c-0908f508f5a4" style="width: 400px; height: 400px;"></div> 1554 * <script type="text/javascript"> 1555 * (function () { 1556 * var board = JXG.JSXGraph.initBoard('JXG0d58cea8-b06a-407c-b27c-0908f508f5a4', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 1557 * var p1 = board.create('point', [6.0, 4.0]); 1558 * var p2 = board.create('point', [3.0, 2.0]); 1559 * var p3 = board.create('point', [1.0, 7.0]); 1560 * var bi1 = board.create('bisector', [p1, p2, p3]); 1561 * })(); 1562 * </script><pre> 1563 */ 1564 JXG.createBisector = function (board, parents, attributes) { 1565 var p, l, i, attr; 1566 1567 parents = Type.providePoints(board, parents, attributes, "point"); 1568 if (Type.isPoint(parents[0]) && Type.isPoint(parents[1]) && Type.isPoint(parents[2])) { 1569 // hidden and fixed helper 1570 attr = Type.copyAttributes(attributes, board.options, "bisector", "point"); 1571 attr.snapToGrid = false; 1572 1573 p = board.create( 1574 "point", 1575 [ 1576 function () { 1577 return Geometry.angleBisector(parents[0], parents[1], parents[2], board); 1578 } 1579 ], 1580 attr 1581 ); 1582 p.dump = false; 1583 1584 for (i = 0; i < 3; i++) { 1585 // required for algorithm requiring dependencies between elements 1586 if (Type.exists(parents[i]._is_new)) { 1587 p.addChild(parents[i]); 1588 delete parents[i]._is_new; 1589 } else { 1590 parents[i].addChild(p); 1591 } 1592 } 1593 1594 if (!Type.exists(attributes.layer)) { 1595 attributes.layer = board.options.layer.line; 1596 } 1597 1598 attr = Type.copyAttributes(attributes, board.options, "bisector"); 1599 l = JXG.createLine(board, [parents[1], p], attr); 1600 1601 /** 1602 * Helper point 1603 * @memberOf Bisector.prototype 1604 * @type Point 1605 * @name point 1606 */ 1607 l.point = p; 1608 1609 l.elType = "bisector"; 1610 l.setParents(parents); 1611 l.subs = { 1612 point: p 1613 }; 1614 l.inherits.push(p); 1615 1616 return l; 1617 } 1618 1619 throw new Error( 1620 "JSXGraph: Can't create angle bisector with parent types '" + 1621 typeof parents[0] + 1622 "' and '" + 1623 typeof parents[1] + 1624 "'." + 1625 "\nPossible parent types: [point,point,point]" 1626 ); 1627 }; 1628 1629 /** 1630 * @class Bisector lines are similar to {@link Bisector} but take two lines as parent elements. The resulting element is 1631 * a composition of two lines. 1632 * @pseudo 1633 * @constructor 1634 * @name Bisectorlines 1635 * @type JXG.Composition 1636 * @augments JXG.Composition 1637 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 1638 * @param {JXG.Line_JXG.Line} l1,l2 The four angles described by the lines <tt>l1</tt> and <tt>l2</tt> will each 1639 * be divided into two equal angles. 1640 * @example 1641 * var p1 = board.create('point', [6.0, 4.0]); 1642 * var p2 = board.create('point', [3.0, 2.0]); 1643 * var p3 = board.create('point', [1.0, 7.0]); 1644 * var p4 = board.create('point', [3.0, 0.0]); 1645 * var l1 = board.create('line', [p1, p2]); 1646 * var l2 = board.create('line', [p3, p4]); 1647 * 1648 * var bi1 = board.create('bisectorlines', [l1, l2]); 1649 * </pre><div class="jxgbox" id="JXG3121ff67-44f0-4dda-bb10-9cda0b80bf18" style="width: 400px; height: 400px;"></div> 1650 * <script type="text/javascript"> 1651 * (function () { 1652 * var board = JXG.JSXGraph.initBoard('JXG3121ff67-44f0-4dda-bb10-9cda0b80bf18', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 1653 * var p1 = board.create('point', [6.0, 4.0]); 1654 * var p2 = board.create('point', [3.0, 2.0]); 1655 * var p3 = board.create('point', [1.0, 7.0]); 1656 * var p4 = board.create('point', [3.0, 0.0]); 1657 * var l1 = board.create('line', [p1, p2]); 1658 * var l2 = board.create('line', [p3, p4]); 1659 * var bi1 = board.create('bisectorlines', [l1, l2]); 1660 * })(); 1661 * </script><pre> 1662 */ 1663 JXG.createAngularBisectorsOfTwoLines = function (board, parents, attributes) { 1664 // The angular bisectors of two line [c1,a1,b1] and [c2,a2,b2] are determined by the equation: 1665 // (a1*x+b1*y+c1*z)/sqrt(a1^2+b1^2) = +/- (a2*x+b2*y+c2*z)/sqrt(a2^2+b2^2) 1666 1667 var g1, 1668 g2, 1669 attr, 1670 ret, 1671 l1 = board.select(parents[0]), 1672 l2 = board.select(parents[1]); 1673 1674 if ( 1675 l1.elementClass !== Const.OBJECT_CLASS_LINE || 1676 l2.elementClass !== Const.OBJECT_CLASS_LINE 1677 ) { 1678 throw new Error( 1679 "JSXGraph: Can't create angle bisectors of two lines with parent types '" + 1680 typeof parents[0] + 1681 "' and '" + 1682 typeof parents[1] + 1683 "'." + 1684 "\nPossible parent types: [line,line]" 1685 ); 1686 } 1687 1688 if (!Type.exists(attributes.layer)) { 1689 attributes.layer = board.options.layer.line; 1690 } 1691 1692 attr = Type.copyAttributes(attributes, board.options, "bisectorlines", "line1"); 1693 g1 = board.create( 1694 "line", 1695 [ 1696 function () { 1697 var d1 = Mat.hypot(l1.stdform[1], l1.stdform[2]), 1698 d2 = Mat.hypot(l2.stdform[1], l2.stdform[2]); 1699 1700 return l1.stdform[0] / d1 - l2.stdform[0] / d2; 1701 }, 1702 function () { 1703 var d1 = Mat.hypot(l1.stdform[1], l1.stdform[2]), 1704 d2 = Mat.hypot(l2.stdform[1], l2.stdform[2]); 1705 1706 return l1.stdform[1] / d1 - l2.stdform[1] / d2; 1707 }, 1708 function () { 1709 var d1 = Mat.hypot(l1.stdform[1], l1.stdform[2]), 1710 d2 = Mat.hypot(l2.stdform[1], l2.stdform[2]); 1711 1712 return l1.stdform[2] / d1 - l2.stdform[2] / d2; 1713 } 1714 ], 1715 attr 1716 ); 1717 1718 if (!Type.exists(attributes.layer)) { 1719 attributes.layer = board.options.layer.line; 1720 } 1721 attr = Type.copyAttributes(attributes, board.options, "bisectorlines", "line2"); 1722 g2 = board.create( 1723 "line", 1724 [ 1725 function () { 1726 var d1 = Mat.hypot(l1.stdform[1], l1.stdform[2]), 1727 d2 = Mat.hypot(l2.stdform[1], l2.stdform[2]); 1728 1729 return l1.stdform[0] / d1 + l2.stdform[0] / d2; 1730 }, 1731 function () { 1732 var d1 = Mat.hypot(l1.stdform[1], l1.stdform[2]), 1733 d2 = Mat.hypot(l2.stdform[1], l2.stdform[2]); 1734 1735 return l1.stdform[1] / d1 + l2.stdform[1] / d2; 1736 }, 1737 function () { 1738 var d1 = Mat.hypot(l1.stdform[1], l1.stdform[2]), 1739 d2 = Mat.hypot(l2.stdform[1], l2.stdform[2]); 1740 1741 return l1.stdform[2] / d1 + l2.stdform[2] / d2; 1742 } 1743 ], 1744 attr 1745 ); 1746 1747 // documentation 1748 /** 1749 * First line. 1750 * @memberOf Bisectorlines.prototype 1751 * @name line1 1752 * @type Line 1753 */ 1754 1755 /** 1756 * Second line. 1757 * @memberOf Bisectorlines.prototype 1758 * @name line2 1759 * @type Line 1760 */ 1761 1762 ret = new Composition({ line1: g1, line2: g2 }); 1763 1764 g1.dump = false; 1765 g2.dump = false; 1766 1767 ret.elType = "bisectorlines"; 1768 ret.setParents([l1.id, l2.id]); 1769 ret.subs = { 1770 line1: g1, 1771 line2: g2 1772 }; 1773 // ret.inherits.push(g1, g2); 1774 1775 return ret; 1776 }; 1777 1778 // /** 1779 // * @class An m-sector is a line which divides an angle into two angles. It is given by three points A, B, and 1780 // * C and a real number m, and divides an angle into two angles, an angle with amplitude m and an angle with 1781 // * amplitude (1-m) 1782 // * @pseudo 1783 // * @constructor 1784 // * @name Msector 1785 // * @type JXG.Line 1786 // * @augments JXG.Line 1787 // * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 1788 // * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 The angle described by <tt>p1</tt>, <tt>p2</tt> and <tt>p3</tt> will 1789 // * be divided into two angles according to the value of <tt>m</tt>. 1790 // * @example 1791 // * var p1 = board.create('point', [6.0, 4.0]); 1792 // * var p2 = board.create('point', [3.0, 2.0]); 1793 // * var p3 = board.create('point', [1.0, 7.0]); 1794 // * 1795 // * var bi1 = board.create('msector', [p1, p2, p3], 1/5); 1796 // * </pre><div id="JXG0d58cea8-b06a-407c-b27c-0908f508f5a4" style="width: 400px; height: 400px;"></div> 1797 // * <script type="text/javascript"> 1798 // * (function () { 1799 // * var board = JXG.JSXGraph.initBoard('JXG0d58cea8-b06a-407c-b27c-0908f508f5a4', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 1800 // * var p1 = board.create('point', [6.0, 4.0]); 1801 // * var p2 = board.create('point', [3.0, 2.0]); 1802 // * var p3 = board.create('point', [1.0, 7.0]); 1803 // * var bi1 = board.create('msector', [p1, p2, p3], 1/5); 1804 // * })(); 1805 // * </script><pre> 1806 // */ 1807 // JXG.createMsector = function (board, parents, attributes) { 1808 // var p, l, i, attr; 1809 1810 // if (parents[0].elementClass === Const.OBJECT_CLASS_POINT && 1811 // parents[1].elementClass === Const.OBJECT_CLASS_POINT && 1812 // parents[2].elementClass === Const.OBJECT_CLASS_POINT) { 1813 // // hidden and fixed helper 1814 // attr = Type.copyAttributes(attributes, board.options, 'msector', 'point'); 1815 // p = board.create('point', [ 1816 // function () { 1817 // return Geometry.angleMsector(parents[0], parents[1], parents[2], parents[3], board); 1818 // } 1819 // ], attr); 1820 // p.dump = false; 1821 1822 // for (i = 0; i < 3; i++) { 1823 // // required for algorithm requiring dependencies between elements 1824 // parents[i].addChild(p); 1825 // } 1826 1827 // if (!Type.exists(attributes.layer)) { 1828 // attributes.layer = board.options.layer.line; 1829 // } 1830 1831 // attr = Type.copyAttributes(attributes, board.options, 'msector'); 1832 // l = JXG.createLine(board, [parents[1], p], attr); 1833 1834 // /** 1835 // * Helper point 1836 // * @memberOf Msector.prototype 1837 // * @type Point 1838 // * @name point 1839 // */ 1840 // l.point = p; 1841 1842 // l.elType = 'msector'; 1843 // l.parents = [parents[0].id, parents[1].id, parents[2].id]; 1844 // l.subs = { 1845 // point: p 1846 // }; 1847 // l.inherits.push(p); 1848 1849 // return l; 1850 // } 1851 1852 // throw new Error("JSXGraph: Can't create angle msector with parent types '" + 1853 // (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 1854 // "\nPossible parent types: [point,point,point,Number]"); 1855 // }; 1856 1857 /** 1858 * @class Constructs the midpoint of a {@link Circumcircle}. Like the circumcircle the circumcenter 1859 * is constructed by providing three points. 1860 * @pseudo 1861 * @description A circumcenter is given by three points which are all lying on the circle with the 1862 * constructed circumcenter as the midpoint. 1863 * @constructor 1864 * @name Circumcenter 1865 * @type JXG.Point 1866 * @augments JXG.Point 1867 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 1868 * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 The constructed point is the midpoint of the circle determined 1869 * by p1, p2, and p3. 1870 * @example 1871 * var p1 = board.create('point', [0.0, 2.0]); 1872 * var p2 = board.create('point', [2.0, 1.0]); 1873 * var p3 = board.create('point', [3.0, 3.0]); 1874 * 1875 * var cc1 = board.create('circumcenter', [p1, p2, p3]); 1876 * </pre><div class="jxgbox" id="JXGe8a40f95-bf30-4eb4-88a8-f4d5495261fd" style="width: 400px; height: 400px;"></div> 1877 * <script type="text/javascript"> 1878 * var ccmex1_board = JXG.JSXGraph.initBoard('JXGe8a40f95-bf30-4eb4-88a8-f4d5495261fd', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 1879 * var ccmex1_p1 = ccmex1_board.create('point', [0.0, 2.0]); 1880 * var ccmex1_p2 = ccmex1_board.create('point', [6.0, 1.0]); 1881 * var ccmex1_p3 = ccmex1_board.create('point', [3.0, 7.0]); 1882 * var ccmex1_cc1 = ccmex1_board.create('circumcenter', [ccmex1_p1, ccmex1_p2, ccmex1_p3]); 1883 * </script><pre> 1884 */ 1885 JXG.createCircumcenter = function (board, parents, attributes) { 1886 var p, i, a, b, c; 1887 1888 parents = Type.providePoints(board, parents, attributes, "point"); 1889 if (Type.isPoint(parents[0]) && Type.isPoint(parents[1]) && Type.isPoint(parents[2])) { 1890 a = parents[0]; 1891 b = parents[1]; 1892 c = parents[2]; 1893 1894 p = JXG.createPoint( 1895 board, 1896 [ 1897 function () { 1898 return Geometry.circumcenter(a, b, c, board); 1899 } 1900 ], 1901 attributes 1902 ); 1903 1904 for (i = 0; i < 3; i++) { 1905 if (Type.exists(parents[i]._is_new)) { 1906 p.addChild(parents[i]); 1907 delete parents[i]._is_new; 1908 } else { 1909 parents[i].addChild(p); 1910 } 1911 } 1912 1913 p.elType = "circumcenter"; 1914 p.setParents(parents); 1915 1916 p.generatePolynomial = function () { 1917 /* 1918 * CircumcircleMidpoint takes three points A, B and C and creates point M, which is the circumcenter of A, B, and C. 1919 * 1920 * 1921 * So we have two conditions: 1922 * 1923 * (a) CT == AT (distance condition I) 1924 * (b) BT == AT (distance condition II) 1925 * 1926 */ 1927 var a1 = a.symbolic.x, 1928 a2 = a.symbolic.y, 1929 b1 = b.symbolic.x, 1930 b2 = b.symbolic.y, 1931 c1 = c.symbolic.x, 1932 c2 = c.symbolic.y, 1933 t1 = p.symbolic.x, 1934 t2 = p.symbolic.y, 1935 poly1 = ["((", t1, ")-(", a1, "))^2+((", t2, ")-(", a2, "))^2-((", t1, ")-(", b1, "))^2-((", t2, ")-(", b2, "))^2"].join(""), 1936 poly2 = ["((", t1, ")-(", a1, "))^2+((", t2, ")-(", a2, "))^2-((", t1, ")-(", c1, "))^2-((", t2, ")-(", c2, "))^2"].join(""); 1937 1938 return [poly1, poly2]; 1939 }; 1940 1941 return p; 1942 } 1943 1944 throw new Error( 1945 "JSXGraph: Can't create circumcircle midpoint with parent types '" + 1946 typeof parents[0] + 1947 "', '" + 1948 typeof parents[1] + 1949 "' and '" + 1950 typeof parents[2] + 1951 "'." + 1952 "\nPossible parent types: [point,point,point]" 1953 ); 1954 }; 1955 1956 /** 1957 * @class Constructs the incenter of the triangle described by the three given points. 1958 * {@link https://mathworld.wolfram.com/Incenter.html} 1959 * @pseudo 1960 * @constructor 1961 * @name Incenter 1962 * @type JXG.Point 1963 * @augments JXG.Point 1964 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 1965 * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 The constructed point is the incenter of the triangle described 1966 * by p1, p2, and p3. 1967 * @example 1968 * var p1 = board.create('point', [0.0, 2.0]); 1969 * var p2 = board.create('point', [2.0, 1.0]); 1970 * var p3 = board.create('point', [3.0, 3.0]); 1971 * 1972 * var ic1 = board.create('incenter', [p1, p2, p3]); 1973 * </pre><div class="jxgbox" id="JXGe8a40f95-bf30-4eb4-88a8-a2d5495261fd" style="width: 400px; height: 400px;"></div> 1974 * <script type="text/javascript"> 1975 * var icmex1_board = JXG.JSXGraph.initBoard('JXGe8a40f95-bf30-4eb4-88a8-a2d5495261fd', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 1976 * var icmex1_p1 = icmex1_board.create('point', [0.0, 2.0]); 1977 * var icmex1_p2 = icmex1_board.create('point', [6.0, 1.0]); 1978 * var icmex1_p3 = icmex1_board.create('point', [3.0, 7.0]); 1979 * var icmex1_ic1 = icmex1_board.create('incenter', [icmex1_p1, icmex1_p2, icmex1_p3]); 1980 * </script><pre> 1981 */ 1982 JXG.createIncenter = function (board, parents, attributes) { 1983 var p, A, B, C, i; 1984 1985 parents = Type.providePoints(board, parents, attributes, "point"); 1986 if ( 1987 parents.length >= 3 && 1988 Type.isPoint(parents[0]) && 1989 Type.isPoint(parents[1]) && 1990 Type.isPoint(parents[2]) 1991 ) { 1992 A = parents[0]; 1993 B = parents[1]; 1994 C = parents[2]; 1995 1996 p = board.create( 1997 "point", 1998 [ 1999 function () { 2000 var a, b, c; 2001 2002 a = Mat.hypot(B.X() - C.X(), B.Y() - C.Y()); 2003 b = Mat.hypot(A.X() - C.X(), A.Y() - C.Y()); 2004 c = Mat.hypot(B.X() - A.X(), B.Y() - A.Y()); 2005 2006 return new Coords( 2007 Const.COORDS_BY_USER, 2008 [ 2009 (a * A.X() + b * B.X() + c * C.X()) / (a + b + c), 2010 (a * A.Y() + b * B.Y() + c * C.Y()) / (a + b + c) 2011 ], 2012 board 2013 ); 2014 } 2015 ], 2016 attributes 2017 ); 2018 2019 for (i = 0; i < 3; i++) { 2020 if (Type.exists(parents[i]._is_new)) { 2021 p.addChild(parents[i]); 2022 delete parents[i]._is_new; 2023 } else { 2024 parents[i].addChild(p); 2025 } 2026 } 2027 2028 p.elType = "incenter"; 2029 p.setParents(parents); 2030 } else { 2031 throw new Error( 2032 "JSXGraph: Can't create incenter with parent types '" + 2033 typeof parents[0] + 2034 "', '" + 2035 typeof parents[1] + 2036 "' and '" + 2037 typeof parents[2] + 2038 "'." + 2039 "\nPossible parent types: [point,point,point]" 2040 ); 2041 } 2042 2043 return p; 2044 }; 2045 2046 /** 2047 * @class A circumcircle is given by three points which are all lying on the circle. 2048 * @pseudo 2049 * @constructor 2050 * @name Circumcircle 2051 * @type JXG.Circle 2052 * @augments JXG.Circle 2053 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 2054 * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 The constructed element is the circle determined by <tt>p1</tt>, <tt>p2</tt>, and <tt>p3</tt>. 2055 * @example 2056 * var p1 = board.create('point', [0.0, 2.0]); 2057 * var p2 = board.create('point', [2.0, 1.0]); 2058 * var p3 = board.create('point', [3.0, 3.0]); 2059 * 2060 * var cc1 = board.create('circumcircle', [p1, p2, p3]); 2061 * </pre><div class="jxgbox" id="JXGe65c9861-0bf0-402d-af57-3ab11962f5ac" style="width: 400px; height: 400px;"></div> 2062 * <script type="text/javascript"> 2063 * var ccex1_board = JXG.JSXGraph.initBoard('JXGe65c9861-0bf0-402d-af57-3ab11962f5ac', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 2064 * var ccex1_p1 = ccex1_board.create('point', [0.0, 2.0]); 2065 * var ccex1_p2 = ccex1_board.create('point', [6.0, 1.0]); 2066 * var ccex1_p3 = ccex1_board.create('point', [3.0, 7.0]); 2067 * var ccex1_cc1 = ccex1_board.create('circumcircle', [ccex1_p1, ccex1_p2, ccex1_p3]); 2068 * </script><pre> 2069 */ 2070 JXG.createCircumcircle = function (board, parents, attributes) { 2071 var p, c, attr, i; 2072 2073 parents = Type.providePoints(board, parents, attributes, "point"); 2074 if (parents === false) { 2075 throw new Error( 2076 "JSXGraph: Can't create circumcircle with parent types '" + 2077 typeof parents[0] + 2078 "', '" + 2079 typeof parents[1] + 2080 "' and '" + 2081 typeof parents[2] + 2082 "'." + 2083 "\nPossible parent types: [point,point,point]" 2084 ); 2085 } 2086 2087 try { 2088 attr = Type.copyAttributes(attributes, board.options, "circumcircle", "center"); 2089 p = JXG.createCircumcenter(board, parents, attr); 2090 2091 p.dump = false; 2092 2093 if (!Type.exists(attributes.layer)) { 2094 attributes.layer = board.options.layer.circle; 2095 } 2096 attr = Type.copyAttributes(attributes, board.options, "circumcircle"); 2097 c = JXG.createCircle(board, [p, parents[0]], attr); 2098 2099 c.elType = "circumcircle"; 2100 c.setParents(parents); 2101 c.subs = { 2102 center: p 2103 }; 2104 c.inherits.push(c); 2105 for (i = 0; i < 3; i++) { 2106 if (Type.exists(parents[i]._is_new)) { 2107 c.addChild(parents[i]); 2108 delete parents[i]._is_new; 2109 } else { 2110 parents[i].addChild(c); 2111 } 2112 } 2113 } catch (e) { 2114 throw new Error( 2115 "JSXGraph: Can't create circumcircle with parent types '" + 2116 typeof parents[0] + 2117 "', '" + 2118 typeof parents[1] + 2119 "' and '" + 2120 typeof parents[2] + 2121 "'." + 2122 "\nPossible parent types: [point,point,point]" 2123 ); 2124 } 2125 2126 // p is already stored as midpoint in c so there's no need to store it explicitly. 2127 2128 return c; 2129 }; 2130 2131 /** 2132 * @class An incircle is given by three points. 2133 * @pseudo 2134 * @constructor 2135 * @name Incircle 2136 * @type JXG.Circle 2137 * @augments JXG.Circle 2138 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 2139 * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 The constructed point is the midpoint of the incircle of 2140 * <tt>p1</tt>, <tt>p2</tt>, and <tt>p3</tt>. 2141 * @example 2142 * var p1 = board.create('point', [0.0, 2.0]); 2143 * var p2 = board.create('point', [2.0, 1.0]); 2144 * var p3 = board.create('point', [3.0, 3.0]); 2145 * 2146 * var ic1 = board.create('incircle', [p1, p2, p3]); 2147 * </pre><div class="jxgbox" id="JXGe65c9861-0bf0-402d-af57-2ab12962f8ac" style="width: 400px; height: 400px;"></div> 2148 * <script type="text/javascript"> 2149 * var icex1_board = JXG.JSXGraph.initBoard('JXGe65c9861-0bf0-402d-af57-2ab12962f8ac', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 2150 * var icex1_p1 = icex1_board.create('point', [0.0, 2.0]); 2151 * var icex1_p2 = icex1_board.create('point', [6.0, 1.0]); 2152 * var icex1_p3 = icex1_board.create('point', [3.0, 7.0]); 2153 * var icex1_ic1 = icex1_board.create('incircle', [icex1_p1, icex1_p2, icex1_p3]); 2154 * </script><pre> 2155 */ 2156 JXG.createIncircle = function (board, parents, attributes) { 2157 var i, p, c, attr; 2158 2159 parents = Type.providePoints(board, parents, attributes, "point"); 2160 if (parents === false) { 2161 throw new Error( 2162 "JSXGraph: Can't create circumcircle with parent types '" + 2163 typeof parents[0] + 2164 "', '" + 2165 typeof parents[1] + 2166 "' and '" + 2167 typeof parents[2] + 2168 "'." + 2169 "\nPossible parent types: [point,point,point]" 2170 ); 2171 } 2172 try { 2173 attr = Type.copyAttributes(attributes, board.options, "incircle", "center"); 2174 p = JXG.createIncenter(board, parents, attr); 2175 2176 p.dump = false; 2177 2178 if (!Type.exists(attributes.layer)) { 2179 attributes.layer = board.options.layer.circle; 2180 } 2181 attr = Type.copyAttributes(attributes, board.options, "incircle"); 2182 c = JXG.createCircle( 2183 board, 2184 [ 2185 p, 2186 function () { 2187 var a = Mat.hypot(parents[1].X() - parents[2].X(), parents[1].Y() - parents[2].Y()), 2188 b = Mat.hypot(parents[0].X() - parents[2].X(), parents[0].Y() - parents[2].Y()), 2189 c = Mat.hypot(parents[1].X() - parents[0].X(), parents[1].Y() - parents[0].Y()), 2190 s = (a + b + c) / 2; 2191 2192 return Math.sqrt(((s - a) * (s - b) * (s - c)) / s); 2193 } 2194 ], 2195 attr 2196 ); 2197 2198 c.elType = "incircle"; 2199 c.setParents(parents); 2200 for (i = 0; i < 3; i++) { 2201 if (Type.exists(parents[i]._is_new)) { 2202 c.addChild(parents[i]); 2203 delete parents[i]._is_new; 2204 } else { 2205 parents[i].addChild(c); 2206 } 2207 } 2208 2209 /** 2210 * The center of the incircle 2211 * @memberOf Incircle.prototype 2212 * @type Incenter 2213 * @name center 2214 */ 2215 c.center = p; 2216 2217 c.subs = { 2218 center: c.center 2219 }; 2220 c.inherits.push(p); 2221 } catch (e) { 2222 throw new Error( 2223 "JSXGraph: Can't create circumcircle with parent types '" + 2224 typeof parents[0] + 2225 "', '" + 2226 typeof parents[1] + 2227 "' and '" + 2228 typeof parents[2] + 2229 "'." + 2230 "\nPossible parent types: [point,point,point]" 2231 ); 2232 } 2233 2234 // p is already stored as midpoint in c so there's no need to store it explicitly. 2235 2236 return c; 2237 }; 2238 2239 /** 2240 * @class This element is used to construct reflected elements (points, lines, circles, curves, polygons). 2241 * @pseudo 2242 * @description A reflected element (point, polygon, line or curve) is given by a given 2243 * object of the same type and a line of reflection. 2244 * It is determined by the reflection of the given element 2245 * across the given line. 2246 * @constructor 2247 * @name Reflection 2248 * @type JXG.GeometryElement 2249 * @augments JXG.GeometryElement 2250 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 2251 * @param {JXG.Point|JXG.Line|JXG.Curve|JXG.Polygon_JXG.Line} p,l The reflection element is the reflection of p across the line l. 2252 * @example 2253 * var p1 = board.create('point', [0.0, 4.0]); 2254 * var p2 = board.create('point', [6.0, 1.0]); 2255 * var l1 = board.create('line', [p1, p2]); 2256 * var p3 = board.create('point', [3.0, 3.0]); 2257 * 2258 * var rp1 = board.create('reflection', [p3, l1]); 2259 * </pre><div class="jxgbox" id="JXG087a798e-a36a-4f52-a2b4-29a23a69393b" style="width: 400px; height: 400px;"></div> 2260 * <script type="text/javascript"> 2261 * var rpex1_board = JXG.JSXGraph.initBoard('JXG087a798e-a36a-4f52-a2b4-29a23a69393b', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 2262 * var rpex1_p1 = rpex1_board.create('point', [0.0, 4.0]); 2263 * var rpex1_p2 = rpex1_board.create('point', [6.0, 1.0]); 2264 * var rpex1_l1 = rpex1_board.create('line', [rpex1_p1, rpex1_p2]); 2265 * var rpex1_p3 = rpex1_board.create('point', [3.0, 3.0]); 2266 * var rpex1_rp1 = rpex1_board.create('reflection', [rpex1_p3, rpex1_l1]); 2267 * </script><pre> 2268 * @example 2269 * // Reflection of more elements 2270 * // reflection line 2271 * var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'}); 2272 * 2273 * var p1 = board.create('point', [-3,-1], {name: "A"}); 2274 * var q1 = board.create('reflection', [p1, li], {name: "A'"}); 2275 * 2276 * var l1 = board.create('line', [1,-5,1]); 2277 * var l2 = board.create('reflection', [l1, li]); 2278 * 2279 * var cu1 = board.create('curve', [[-3, -3, -2.5, -3, -3, -2.5], [-3, -2, -2, -2, -2.5, -2.5]], {strokeWidth:3}); 2280 * var cu2 = board.create('reflection', [cu1, li], {strokeColor: 'red', strokeWidth:3}); 2281 * 2282 * var pol1 = board.create('polygon', [[-6,-3], [-4,-5], [-5,-1.5]]); 2283 * var pol2 = board.create('reflection', [pol1, li]); 2284 * 2285 * var c1 = board.create('circle', [[-2,-2], [-2, -1]]); 2286 * var c2 = board.create('reflection', [c1, li]); 2287 * 2288 * var a1 = board.create('arc', [[1, 1], [0, 1], [1, 0]], {strokeColor: 'red'}); 2289 * var a2 = board.create('reflection', [a1, li], {strokeColor: 'red'}); 2290 * 2291 * var s1 = board.create('sector', [[-3.5,-3], [-3.5, -2], [-3.5,-4]], { 2292 * anglePoint: {visible:true}, center: {visible: true}, radiusPoint: {visible: true}, 2293 * fillColor: 'yellow', strokeColor: 'black'}); 2294 * var s2 = board.create('reflection', [s1, li], {fillColor: 'yellow', strokeColor: 'black', fillOpacity: 0.5}); 2295 * 2296 * var an1 = board.create('angle', [[-4,3.9], [-3, 4], [-3, 3]]); 2297 * var an2 = board.create('reflection', [an1, li]); 2298 * 2299 * </pre><div id="JXG8f763af4-d449-11e7-93b3-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 2300 * <script type="text/javascript"> 2301 * (function() { 2302 * var board = JXG.JSXGraph.initBoard('JXG8f763af4-d449-11e7-93b3-901b0e1b8723', 2303 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 2304 * // reflection line 2305 * var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'}); 2306 * 2307 * var p1 = board.create('point', [-3,-1], {name: "A"}); 2308 * var q1 = board.create('reflection', [p1, li], {name: "A'"}); 2309 * 2310 * var l1 = board.create('line', [1,-5,1]); 2311 * var l2 = board.create('reflection', [l1, li]); 2312 * 2313 * var cu1 = board.create('curve', [[-3, -3, -2.5, -3, -3, -2.5], [-3, -2, -2, -2, -2.5, -2.5]], {strokeWidth:3}); 2314 * var cu2 = board.create('reflection', [cu1, li], {strokeColor: 'red', strokeWidth:3}); 2315 * 2316 * var pol1 = board.create('polygon', [[-6,-3], [-4,-5], [-5,-1.5]]); 2317 * var pol2 = board.create('reflection', [pol1, li]); 2318 * 2319 * var c1 = board.create('circle', [[-2,-2], [-2, -1]]); 2320 * var c2 = board.create('reflection', [c1, li]); 2321 * 2322 * var a1 = board.create('arc', [[1, 1], [0, 1], [1, 0]], {strokeColor: 'red'}); 2323 * var a2 = board.create('reflection', [a1, li], {strokeColor: 'red'}); 2324 * 2325 * var s1 = board.create('sector', [[-3.5,-3], [-3.5, -2], [-3.5,-4]], { 2326 * anglePoint: {visible:true}, center: {visible: true}, radiusPoint: {visible: true}, 2327 * fillColor: 'yellow', strokeColor: 'black'}); 2328 * var s2 = board.create('reflection', [s1, li], {fillColor: 'yellow', strokeColor: 'black', fillOpacity: 0.5}); 2329 * 2330 * var an1 = board.create('angle', [[-4,3.9], [-3, 4], [-3, 3]]); 2331 * var an2 = board.create('reflection', [an1, li]); 2332 * 2333 * })(); 2334 * 2335 * </script><pre> 2336 * 2337 */ 2338 JXG.createReflection = function (board, parents, attributes) { 2339 var l, org, r, r_c, 2340 t, i, attr, attr2, 2341 errStr = "\nPossible parent types: [point|line|curve|polygon|circle|arc|sector, line]"; 2342 2343 for (i = 0; i < parents.length; ++i) { 2344 parents[i] = board.select(parents[i]); 2345 } 2346 2347 attr = Type.copyAttributes(attributes, board.options, "reflection"); 2348 2349 if (Type.isPoint(parents[0])) { 2350 org = Type.providePoints(board, [parents[0]], attr2)[0]; 2351 } else if ( 2352 parents[0].elementClass === Const.OBJECT_CLASS_CURVE || 2353 parents[0].elementClass === Const.OBJECT_CLASS_LINE || 2354 parents[0].type === Const.OBJECT_TYPE_POLYGON || 2355 parents[0].elementClass === Const.OBJECT_CLASS_CIRCLE 2356 ) { 2357 org = parents[0]; 2358 } else { 2359 throw new Error( 2360 "JSXGraph: Can't create reflection element with parent types '" + 2361 typeof parents[0] + 2362 "' and '" + 2363 typeof parents[1] + 2364 "'." + 2365 errStr 2366 ); 2367 } 2368 2369 if (parents[1].elementClass === Const.OBJECT_CLASS_LINE) { 2370 l = parents[1]; 2371 } else { 2372 throw new Error( 2373 "JSXGraph: Can't create reflected element with parent types '" + 2374 typeof parents[0] + 2375 "' and '" + 2376 typeof parents[1] + 2377 "'." + 2378 errStr 2379 ); 2380 } 2381 t = JXG.createTransform(board, [l], { type: "reflect" }); 2382 2383 if (Type.isPoint(org)) { 2384 r = JXG.createPoint(board, [org, t], attr); 2385 2386 // Arcs and sectors are treated as curves 2387 } else if (org.elementClass === Const.OBJECT_CLASS_CURVE) { 2388 r = JXG.createCurve(board, [org, t], attr); 2389 } else if (org.elementClass === Const.OBJECT_CLASS_LINE) { 2390 r = JXG.createLine(board, [org, t], attr); 2391 } else if (org.type === Const.OBJECT_TYPE_POLYGON) { 2392 r = JXG.createPolygon(board, [org, t], attr); 2393 } else if (org.elementClass === Const.OBJECT_CLASS_CIRCLE) { 2394 if (attr.type.toLowerCase() === "euclidean") { 2395 // Create a circle element from a circle and a Euclidean transformation 2396 attr2 = Type.copyAttributes(attributes, board.options, "reflection", "center"); 2397 r_c = JXG.createPoint(board, [org.center, t], attr2); 2398 r_c.prepareUpdate() 2399 .update() 2400 .updateVisibility(Type.evaluate(r_c.visProp.visible)) 2401 .updateRenderer(); 2402 r = JXG.createCircle( 2403 board, 2404 [ 2405 r_c, 2406 function () { 2407 return org.Radius(); 2408 } 2409 ], 2410 attr 2411 ); 2412 } else { 2413 // Create a conic element from a circle and a projective transformation 2414 r = JXG.createCircle(board, [org, t], attr); 2415 } 2416 } else { 2417 throw new Error( 2418 "JSXGraph: Can't create reflected element with parent types '" + 2419 typeof parents[0] + 2420 "' and '" + 2421 typeof parents[1] + 2422 "'." + 2423 errStr 2424 ); 2425 } 2426 2427 if (Type.exists(org._is_new)) { 2428 r.addChild(org); 2429 delete org._is_new; 2430 } else { 2431 // org.addChild(r); 2432 } 2433 l.addChild(r); 2434 2435 r.elType = "reflection"; 2436 r.addParents(l); 2437 r.prepareUpdate().update(); //.updateVisibility(Type.evaluate(r.visProp.visible)).updateRenderer(); 2438 2439 if (Type.isPoint(r)) { 2440 r.generatePolynomial = function () { 2441 /* 2442 * Reflection takes a point R and a line L and creates point P, which is the reflection of R on L. 2443 * L is defined by two points A and B. 2444 * 2445 * So we have two conditions: 2446 * 2447 * (a) RP _|_ AB (orthogonality condition) 2448 * (b) AR == AP (distance condition) 2449 * 2450 */ 2451 var a1 = l.point1.symbolic.x, 2452 a2 = l.point1.symbolic.y, 2453 b1 = l.point2.symbolic.x, 2454 b2 = l.point2.symbolic.y, 2455 p1 = org.symbolic.x, 2456 p2 = org.symbolic.y, 2457 r1 = r.symbolic.x, 2458 r2 = r.symbolic.y, 2459 poly1 = ["((", r2, ")-(", p2, "))*((", a2, ")-(", b2, "))+((", a1, ")-(", b1, "))*((", r1, ")-(", p1, "))"].join(""), 2460 poly2 = ["((", r1, ")-(", a1, "))^2+((", r2, ")-(", a2, "))^2-((", p1, ")-(", a1, "))^2-((", p2, ")-(", a2, "))^2"].join(""); 2461 2462 return [poly1, poly2]; 2463 }; 2464 } 2465 2466 return r; 2467 }; 2468 2469 /** 2470 * @class A mirror element of a point, line, circle, curve, polygon will be constructed. 2471 * @pseudo 2472 * @description A mirror element is determined by the reflection of a given point, line, circle, curve, polygon across another given point. 2473 * @constructor 2474 * @name mirrorelement 2475 * @type JXG.GeometryElement 2476 * @augments JXG.GeometryElement 2477 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 2478 * @param {JXG.Point|JXG.Line|JXG.Curve|JXG.Ppolygon_JXG.Point} p1,p2 The constructed element is the mirror image of p2 across p1. 2479 * @example 2480 * // point of reflection 2481 * var mirr = board.create('point', [-1,-1], {color: '#aaaaaa'}); 2482 * 2483 * var p1 = board.create('point', [-3,-1], {name: "A"}); 2484 * var q1 = board.create('mirrorelement', [p1, mirr], {name: "A'"}); 2485 * 2486 * var l1 = board.create('line', [1, -5, 1]); 2487 * var l2 = board.create('mirrorelement', [l1, mirr]); 2488 * 2489 * var cu1 = board.create('curve', [[-3, -3, -2.5, -3, -3, -2.5], [-3, -2, -2, -2, -2.5, -2.5]], {strokeWidth:3}); 2490 * var cu2 = board.create('mirrorelement', [cu1, mirr], {strokeColor: 'red', strokeWidth:3}); 2491 * 2492 * var pol1 = board.create('polygon', [[-6,-2], [-4,-4], [-5,-0.5]]); 2493 * var pol2 = board.create('mirrorelement', [pol1, mirr]); 2494 * 2495 * var c1 = board.create('circle', [[-6,-6], [-6, -5]]); 2496 * var c2 = board.create('mirrorelement', [c1, mirr]); 2497 * 2498 * var a1 = board.create('arc', [[1, 1], [0, 1], [1, 0]], {strokeColor: 'red'}); 2499 * var a2 = board.create('mirrorelement', [a1, mirr], {strokeColor: 'red'}); 2500 * 2501 * var s1 = board.create('sector', [[-3.5,-3], [-3.5, -2], [-3.5,-4]], { 2502 * anglePoint: {visible:true}, center: {visible: true}, radiusPoint: {visible: true}, 2503 * fillColor: 'yellow', strokeColor: 'black'}); 2504 * var s2 = board.create('mirrorelement', [s1, mirr], {fillColor: 'yellow', strokeColor: 'black', fillOpacity: 0.5}); 2505 * 2506 * var an1 = board.create('angle', [[-4,3.9], [-3, 4], [-3, 3]]); 2507 * var an2 = board.create('mirrorelement', [an1, mirr]); 2508 * 2509 * 2510 * </pre><div id="JXG026c779c-d8d9-11e7-93b3-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 2511 * <script type="text/javascript"> 2512 * (function() { 2513 * var board = JXG.JSXGraph.initBoard('JXG026c779c-d8d9-11e7-93b3-901b0e1b8723', 2514 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 2515 * // point of reflection 2516 * var mirr = board.create('point', [-1,-1], {color: '#aaaaaa'}); 2517 * 2518 * var p1 = board.create('point', [-3,-1], {name: "A"}); 2519 * var q1 = board.create('mirrorelement', [p1, mirr], {name: "A'"}); 2520 * 2521 * var l1 = board.create('line', [1,-5, 1]); 2522 * var l2 = board.create('mirrorelement', [l1, mirr]); 2523 * 2524 * var cu1 = board.create('curve', [[-3, -3, -2.5, -3, -3, -2.5], [-3, -2, -2, -2, -2.5, -2.5]], {strokeWidth:3}); 2525 * var cu2 = board.create('mirrorelement', [cu1, mirr], {strokeColor: 'red', strokeWidth:3}); 2526 * 2527 * var pol1 = board.create('polygon', [[-6,-2], [-4,-4], [-5,-0.5]]); 2528 * var pol2 = board.create('mirrorelement', [pol1, mirr]); 2529 * 2530 * var c1 = board.create('circle', [[-6,-6], [-6, -5]]); 2531 * var c2 = board.create('mirrorelement', [c1, mirr]); 2532 * 2533 * var a1 = board.create('arc', [[1, 1], [0, 1], [1, 0]], {strokeColor: 'red'}); 2534 * var a2 = board.create('mirrorelement', [a1, mirr], {strokeColor: 'red'}); 2535 * 2536 * var s1 = board.create('sector', [[-3.5,-3], [-3.5, -2], [-3.5,-4]], { 2537 * anglePoint: {visible:true}, center: {visible: true}, radiusPoint: {visible: true}, 2538 * fillColor: 'yellow', strokeColor: 'black'}); 2539 * var s2 = board.create('mirrorelement', [s1, mirr], {fillColor: 'yellow', strokeColor: 'black', fillOpacity: 0.5}); 2540 * 2541 * var an1 = board.create('angle', [[-4,3.9], [-3, 4], [-3, 3]]); 2542 * var an2 = board.create('mirrorelement', [an1, mirr]); 2543 * 2544 * })(); 2545 * 2546 * </script><pre> 2547 */ 2548 JXG.createMirrorElement = function (board, parents, attributes) { 2549 var org, i, m, r, r_c, t, 2550 attr, attr2, 2551 errStr = "\nPossible parent types: [point|line|curve|polygon|circle|arc|sector, point]"; 2552 2553 for (i = 0; i < parents.length; ++i) { 2554 parents[i] = board.select(parents[i]); 2555 } 2556 2557 attr = Type.copyAttributes(attributes, board.options, "mirrorelement"); 2558 if (Type.isPoint(parents[0])) { 2559 // Create point to be mirrored if supplied by coords array. 2560 org = Type.providePoints(board, [parents[0]], attr)[0]; 2561 } else if ( 2562 parents[0].elementClass === Const.OBJECT_CLASS_CURVE || 2563 parents[0].elementClass === Const.OBJECT_CLASS_LINE || 2564 parents[0].type === Const.OBJECT_TYPE_POLYGON || 2565 parents[0].elementClass === Const.OBJECT_CLASS_CIRCLE 2566 ) { 2567 org = parents[0]; 2568 } else { 2569 throw new Error( 2570 "JSXGraph: Can't create mirror element with parent types '" + 2571 typeof parents[0] + 2572 "' and '" + 2573 typeof parents[1] + 2574 "'." + 2575 errStr 2576 ); 2577 } 2578 2579 if (Type.isPoint(parents[1])) { 2580 attr2 = Type.copyAttributes(attributes, board.options, "mirrorelement", "point"); 2581 // Create mirror point if supplied by coords array. 2582 m = Type.providePoints(board, [parents[1]], attr2)[0]; 2583 } else { 2584 throw new Error( 2585 "JSXGraph: Can't create mirror element with parent types '" + 2586 typeof parents[0] + 2587 "' and '" + 2588 typeof parents[1] + 2589 "'." + 2590 errStr 2591 ); 2592 } 2593 2594 t = JXG.createTransform(board, [Math.PI, m], { type: "rotate" }); 2595 if (Type.isPoint(org)) { 2596 r = JXG.createPoint(board, [org, t], attr); 2597 2598 // Arcs and sectors are treated as curves 2599 } else if (org.elementClass === Const.OBJECT_CLASS_CURVE) { 2600 r = JXG.createCurve(board, [org, t], attr); 2601 } else if (org.elementClass === Const.OBJECT_CLASS_LINE) { 2602 r = JXG.createLine(board, [org, t], attr); 2603 } else if (org.type === Const.OBJECT_TYPE_POLYGON) { 2604 r = JXG.createPolygon(board, [org, t], attr); 2605 } else if (org.elementClass === Const.OBJECT_CLASS_CIRCLE) { 2606 if (attr.type.toLowerCase() === "euclidean") { 2607 // Create a circle element from a circle and a Euclidean transformation 2608 attr2 = Type.copyAttributes(attributes, board.options, "mirrorelement", "center"); 2609 r_c = JXG.createPoint(board, [org.center, t], attr2); 2610 r_c.prepareUpdate() 2611 .update() 2612 .updateVisibility(Type.evaluate(r_c.visProp.visible)) 2613 .updateRenderer(); 2614 r = JXG.createCircle( 2615 board, 2616 [ 2617 r_c, 2618 function () { 2619 return org.Radius(); 2620 } 2621 ], 2622 attr 2623 ); 2624 } else { 2625 // Create a conic element from a circle and a projective transformation 2626 r = JXG.createCircle(board, [org, t], attr); 2627 } 2628 } else { 2629 throw new Error( 2630 "JSXGraph: Can't create mirror element with parent types '" + 2631 typeof parents[0] + 2632 "' and '" + 2633 typeof parents[1] + 2634 "'." + 2635 errStr 2636 ); 2637 } 2638 2639 if (Type.exists(org._is_new)) { 2640 r.addChild(org); 2641 delete org._is_new; 2642 } else { 2643 // org.addChild(r); 2644 } 2645 m.addChild(r); 2646 2647 r.elType = "mirrorelement"; 2648 r.addParents(m); 2649 r.prepareUpdate().update(); 2650 2651 return r; 2652 }; 2653 2654 /** 2655 * @class A mirror point will be constructed. 2656 * @pseudo 2657 * @description A mirror point is determined by the reflection of a given point against another given point. 2658 * @constructor 2659 * @name Mirrorpoint 2660 * @type JXG.Point 2661 * @augments JXG.Point 2662 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 2663 * @param {JXG.Point_JXG.Point} p1,p2 The constructed point is the reflection of p2 against p1. 2664 * 2665 * This method is superseeded by the more general {@link JXG.createMirrorElement}. 2666 * @example 2667 * var p1 = board.create('point', [3.0, 3.0]); 2668 * var p2 = board.create('point', [6.0, 1.0]); 2669 * 2670 * var mp1 = board.create('mirrorpoint', [p1, p2]); 2671 * </pre><div class="jxgbox" id="JXG7eb2a814-6c4b-4caa-8cfa-4183a948d25b" style="width: 400px; height: 400px;"></div> 2672 * <script type="text/javascript"> 2673 * var mpex1_board = JXG.JSXGraph.initBoard('JXG7eb2a814-6c4b-4caa-8cfa-4183a948d25b', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 2674 * var mpex1_p1 = mpex1_board.create('point', [3.0, 3.0]); 2675 * var mpex1_p2 = mpex1_board.create('point', [6.0, 1.0]); 2676 * var mpex1_mp1 = mpex1_board.create('mirrorpoint', [mpex1_p1, mpex1_p2]); 2677 * </script><pre> 2678 */ 2679 JXG.createMirrorPoint = function (board, parents, attributes) { 2680 var el = JXG.createMirrorElement(board, parents, attributes); 2681 el.elType = "mirrorpoint"; 2682 return el; 2683 }; 2684 2685 /** 2686 * @class This element is used to visualize the integral of a given curve over a given interval. 2687 * @pseudo 2688 * @description The Integral element is used to visualize the area under a given curve over a given interval 2689 * and to calculate the area's value. For that a polygon and gliders are used. The polygon displays the area, 2690 * the gliders are used to change the interval dynamically. 2691 * @constructor 2692 * @name Integral 2693 * @type JXG.Curve 2694 * @augments JXG.Curve 2695 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 2696 * @param {Array_JXG.Curve} i,c The constructed element covers the area between the curve <tt>c</tt> and the x-axis 2697 * within the interval <tt>i</tt>. 2698 * @example 2699 * var c1 = board.create('functiongraph', [function (t) { return Math.cos(t)*t; }]); 2700 * var i1 = board.create('integral', [[-2.0, 2.0], c1]); 2701 * </pre><div class="jxgbox" id="JXGd45d7188-6624-4d6e-bebb-1efa2a305c8a" style="width: 400px; height: 400px;"></div> 2702 * <script type="text/javascript"> 2703 * var intex1_board = JXG.JSXGraph.initBoard('JXGd45d7188-6624-4d6e-bebb-1efa2a305c8a', {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright: false, shownavigation: false}); 2704 * var intex1_c1 = intex1_board.create('functiongraph', [function (t) { return Math.cos(t)*t; }]); 2705 * var intex1_i1 = intex1_board.create('integral', [[-2.0, 2.0], intex1_c1]); 2706 * </script><pre> 2707 */ 2708 JXG.createIntegral = function (board, parents, attributes) { 2709 var interval, curve, attr, start, end, 2710 startx, starty, endx, endy, 2711 pa_on_curve, pa_on_axis, pb_on_curve, pb_on_axis, 2712 txt_fun, 2713 t = null, p; 2714 2715 if (Type.isArray(parents[0]) && parents[1].elementClass === Const.OBJECT_CLASS_CURVE) { 2716 interval = parents[0]; 2717 curve = parents[1]; 2718 } else if ( 2719 Type.isArray(parents[1]) && 2720 parents[0].elementClass === Const.OBJECT_CLASS_CURVE 2721 ) { 2722 interval = parents[1]; 2723 curve = parents[0]; 2724 } else { 2725 throw new Error( 2726 "JSXGraph: Can't create integral with parent types '" + 2727 typeof parents[0] + 2728 "' and '" + 2729 typeof parents[1] + 2730 "'." + 2731 "\nPossible parent types: [[number|function,number|function],curve]" 2732 ); 2733 } 2734 2735 attr = Type.copyAttributes(attributes, board.options, "integral"); 2736 attr.withLabel = false; // There is a custom 'label' below. 2737 p = board.create("curve", [[0], [0]], attr); 2738 2739 // Correct the interval if necessary - NOT ANYMORE, GGB's fault 2740 start = interval[0]; 2741 end = interval[1]; 2742 2743 if (Type.isFunction(start)) { 2744 startx = start; 2745 starty = function () { 2746 return curve.Y(startx()); 2747 }; 2748 start = startx(); 2749 } else { 2750 startx = start; 2751 starty = curve.Y(start); 2752 } 2753 2754 if (Type.isFunction(end)) { 2755 endx = end; 2756 endy = function () { 2757 return curve.Y(endx()); 2758 }; 2759 end = endx(); 2760 } else { 2761 endx = end; 2762 endy = curve.Y(end); 2763 } 2764 2765 attr = Type.copyAttributes(attributes, board.options, "integral", "curveLeft"); 2766 pa_on_curve = board.create("glider", [startx, starty, curve], attr); 2767 if (Type.isFunction(startx)) { 2768 pa_on_curve.hideElement(); 2769 } 2770 2771 attr = Type.copyAttributes(attributes, board.options, 'integral', 'baseLeft'); 2772 pa_on_axis = board.create('point', [ 2773 function () { 2774 if (Type.evaluate(p.visProp.axis) === "y") { 2775 return 0; 2776 } 2777 return pa_on_curve.X(); 2778 }, 2779 function () { 2780 if (Type.evaluate(p.visProp.axis) === "y") { 2781 return pa_on_curve.Y(); 2782 } 2783 return 0; 2784 } 2785 ], attr); 2786 2787 attr = Type.copyAttributes(attributes, board.options, "integral", "curveRight"); 2788 pb_on_curve = board.create("glider", [endx, endy, curve], attr); 2789 if (Type.isFunction(endx)) { 2790 pb_on_curve.hideElement(); 2791 } 2792 2793 attr = Type.copyAttributes(attributes, board.options, "integral", "baseRight"); 2794 pb_on_axis = board.create('point', [ 2795 function () { 2796 if (Type.evaluate(p.visProp.axis) === "y") { 2797 return 0; 2798 } 2799 return pb_on_curve.X(); 2800 }, 2801 function () { 2802 if (Type.evaluate(p.visProp.axis) === "y") { 2803 return pb_on_curve.Y(); 2804 } 2805 2806 return 0; 2807 } 2808 ], attr); 2809 2810 attr = Type.copyAttributes(attributes, board.options, "integral"); 2811 if (attr.withlabel !== false && attr.axis !== "y") { 2812 attr = Type.copyAttributes(attributes, board.options, "integral", "label"); 2813 attr = Type.copyAttributes(attr, board.options, "label"); 2814 2815 t = board.create('text', [ 2816 function () { 2817 var off = new Coords( 2818 Const.COORDS_BY_SCREEN, 2819 [ 2820 Type.evaluate(this.visProp.offset[0]) + 2821 this.board.origin.scrCoords[1], 2822 0 2823 ], 2824 this.board, 2825 false 2826 ), 2827 bb = this.board.getBoundingBox(), 2828 dx = (bb[2] - bb[0]) * 0.1, 2829 x = pb_on_curve.X(); 2830 2831 if (x < bb[0]) { 2832 x = bb[0] + dx; 2833 } else if (x > bb[2]) { 2834 x = bb[2] - dx; 2835 } 2836 2837 return x + off.usrCoords[1]; 2838 }, 2839 function () { 2840 var off = new Coords( 2841 Const.COORDS_BY_SCREEN, 2842 [ 2843 0, 2844 Type.evaluate(this.visProp.offset[1]) + 2845 this.board.origin.scrCoords[2] 2846 ], 2847 this.board, 2848 false 2849 ), 2850 bb = this.board.getBoundingBox(), 2851 dy = (bb[1] - bb[3]) * 0.1, 2852 y = pb_on_curve.Y(); 2853 2854 if (y > bb[1]) { 2855 y = bb[1] - dy; 2856 } else if (y < bb[3]) { 2857 y = bb[3] + dy; 2858 } 2859 2860 return y + off.usrCoords[2]; 2861 }, 2862 '' 2863 ], attr); 2864 2865 txt_fun = function () { 2866 var Int = Numerics.NewtonCotes([pa_on_axis.X(), pb_on_axis.X()], curve.Y), 2867 digits = Type.evaluate(t.visProp.digits), 2868 val; 2869 2870 if (t.useLocale()) { 2871 val = t.formatNumberLocale(Int, digits); 2872 } else { 2873 val = Type.toFixed(Int, digits); 2874 } 2875 return '∫ = ' + val; 2876 }; 2877 t.setText(txt_fun); 2878 t.dump = false; 2879 2880 pa_on_curve.addChild(t); 2881 pb_on_curve.addChild(t); 2882 } 2883 2884 // dump stuff 2885 pa_on_curve.dump = false; 2886 pa_on_axis.dump = false; 2887 2888 pb_on_curve.dump = false; 2889 pb_on_axis.dump = false; 2890 2891 p.elType = "integral"; 2892 p.setParents([curve.id, interval]); 2893 p.subs = { 2894 curveLeft: pa_on_curve, 2895 baseLeft: pa_on_axis, 2896 curveRight: pb_on_curve, 2897 baseRight: pb_on_axis 2898 }; 2899 p.inherits.push(pa_on_curve, pa_on_axis, pb_on_curve, pb_on_axis); 2900 2901 if (attr.withLabel) { 2902 p.subs.label = t; 2903 p.inherits.push(t); 2904 } 2905 2906 /** 2907 * Returns the current value of the integral. 2908 * @memberOf Integral 2909 * @name Value 2910 * @function 2911 * @returns {Number} 2912 */ 2913 p.Value = function () { 2914 return Numerics.I([pa_on_axis.X(), pb_on_axis.X()], curve.Y); 2915 }; 2916 2917 /** 2918 * documented in JXG.Curve 2919 * @class 2920 * @ignore 2921 */ 2922 p.updateDataArray = function () { 2923 var x, y, i, left, right, lowx, upx, lowy, upy; 2924 2925 if (Type.evaluate(this.visProp.axis) === "y") { 2926 if (pa_on_curve.Y() < pb_on_curve.Y()) { 2927 lowx = pa_on_curve.X(); 2928 lowy = pa_on_curve.Y(); 2929 upx = pb_on_curve.X(); 2930 upy = pb_on_curve.Y(); 2931 } else { 2932 lowx = pb_on_curve.X(); 2933 lowy = pb_on_curve.Y(); 2934 upx = pa_on_curve.X(); 2935 upy = pa_on_curve.Y(); 2936 } 2937 left = Math.min(lowx, upx); 2938 right = Math.max(lowx, upx); 2939 2940 x = [0, lowx]; 2941 y = [lowy, lowy]; 2942 2943 for (i = 0; i < curve.numberPoints; i++) { 2944 if ( 2945 lowy <= curve.points[i].usrCoords[2] && 2946 left <= curve.points[i].usrCoords[1] && 2947 curve.points[i].usrCoords[2] <= upy && 2948 curve.points[i].usrCoords[1] <= right 2949 ) { 2950 x.push(curve.points[i].usrCoords[1]); 2951 y.push(curve.points[i].usrCoords[2]); 2952 } 2953 } 2954 x.push(upx); 2955 y.push(upy); 2956 x.push(0); 2957 y.push(upy); 2958 2959 // close the curve 2960 x.push(0); 2961 y.push(lowy); 2962 } else { 2963 if (pa_on_axis.X() < pb_on_axis.X()) { 2964 left = pa_on_axis.X(); 2965 right = pb_on_axis.X(); 2966 } else { 2967 left = pb_on_axis.X(); 2968 right = pa_on_axis.X(); 2969 } 2970 2971 x = [left, left]; 2972 y = [0, curve.Y(left)]; 2973 2974 for (i = 0; i < curve.numberPoints; i++) { 2975 if ( 2976 left <= curve.points[i].usrCoords[1] && 2977 curve.points[i].usrCoords[1] <= right 2978 ) { 2979 x.push(curve.points[i].usrCoords[1]); 2980 y.push(curve.points[i].usrCoords[2]); 2981 } 2982 } 2983 x.push(right); 2984 y.push(curve.Y(right)); 2985 x.push(right); 2986 y.push(0); 2987 2988 // close the curve 2989 x.push(left); 2990 y.push(0); 2991 } 2992 2993 this.dataX = x; 2994 this.dataY = y; 2995 }; 2996 2997 pa_on_curve.addChild(p); 2998 pb_on_curve.addChild(p); 2999 pa_on_axis.addChild(p); 3000 pb_on_axis.addChild(p); 3001 3002 /** 3003 * The point on the axis initially corresponding to the lower value of the interval. 3004 * 3005 * @name baseLeft 3006 * @memberOf Integral 3007 * @type JXG.Point 3008 */ 3009 p.baseLeft = pa_on_axis; 3010 3011 /** 3012 * The point on the axis initially corresponding to the higher value of the interval. 3013 * 3014 * @name baseRight 3015 * @memberOf Integral 3016 * @type JXG.Point 3017 */ 3018 p.baseRight = pb_on_axis; 3019 3020 /** 3021 * The glider on the curve corresponding to the lower value of the interval. 3022 * 3023 * @name curveLeft 3024 * @memberOf Integral 3025 * @type Glider 3026 */ 3027 p.curveLeft = pa_on_curve; 3028 3029 /** 3030 * The glider on the axis corresponding to the higher value of the interval. 3031 * 3032 * @name curveRight 3033 * @memberOf Integral 3034 * @type Glider 3035 */ 3036 p.curveRight = pb_on_curve; 3037 3038 p.methodMap = JXG.deepCopy(p.methodMap, { 3039 curveLeft: "curveLeft", 3040 baseLeft: "baseLeft", 3041 curveRight: "curveRight", 3042 baseRight: "baseRight", 3043 Value: "Value" 3044 }); 3045 3046 /** 3047 * documented in GeometryElement 3048 * @ignore 3049 */ 3050 p.label = t; 3051 3052 return p; 3053 }; 3054 3055 /** 3056 * @class Creates a grid to support the user with element placement. 3057 * @pseudo 3058 * @description A grid is a set of vertical and horizontal lines to support the user with element placement. This method 3059 * draws such a grid on the given board. This method does not 3060 * take any parent elements. It is usually instantiated on the board's creation via the attribute <tt>grid</tt> set 3061 * to true. 3062 * @parameter None. 3063 * @constructor 3064 * @name Grid 3065 * @type JXG.Curve 3066 * @augments JXG.Curve 3067 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 3068 * @example 3069 * grid = board.create('grid', []); 3070 * </pre><div class="jxgbox" id="JXGa9a0671f-7a51-4fa2-8697-241142c00940" style="width: 400px; height: 400px;"></div> 3071 * <script type="text/javascript"> 3072 * (function () { 3073 * board = JXG.JSXGraph.initBoard('JXGa9a0671f-7a51-4fa2-8697-241142c00940', {boundingbox:[-4, 6, 10, -6], axis: false, grid: false, keepaspectratio: true}); 3074 * grid = board.create('grid', []); 3075 * })(); 3076 * </script><pre> 3077 */ 3078 JXG.createGrid = function (board, parents, attributes) { 3079 var c, attr; 3080 3081 attr = Type.copyAttributes(attributes, board.options, "grid"); 3082 c = board.create("curve", [[null], [null]], attr); 3083 3084 c.elType = "grid"; 3085 c.type = Const.OBJECT_TYPE_GRID; 3086 3087 /** 3088 * @ignore 3089 */ 3090 c.updateDataArray = function () { 3091 var start, end, i, 3092 max_lines = 5000, 3093 is_finite, 3094 topLeft, bottomRight, 3095 gridX = Type.evaluate(this.visProp.gridx), 3096 gridY = Type.evaluate(this.visProp.gridy); 3097 3098 if (Type.isArray(this.visProp.topleft)) { 3099 topLeft = new Coords( 3100 Type.evaluate(this.visProp.tltype) || Const.COORDS_BY_USER, 3101 this.visProp.topleft, 3102 board 3103 ); 3104 } else { 3105 topLeft = new Coords(Const.COORDS_BY_SCREEN, [0, 0], board); 3106 } 3107 3108 if (Type.isArray(this.visProp.bottomright)) { 3109 bottomRight = new Coords( 3110 Type.evaluate(this.visProp.brtype) || Const.COORDS_BY_USER, 3111 this.visProp.bottomright, 3112 board 3113 ); 3114 } else { 3115 bottomRight = new Coords( 3116 Const.COORDS_BY_SCREEN, 3117 [board.canvasWidth, board.canvasHeight], 3118 board 3119 ); 3120 } 3121 3122 // 3123 // | | | 3124 // ----+---------+---------+----- 3125 // | /| | 3126 // | gridY| <---+------ Grid Cell 3127 // | \| | 3128 // ----+---------+---------+----- 3129 // | |\ gridX /| 3130 // | | | 3131 // 3132 // uc: usercoordinates 3133 // 3134 // currently one grid cell is 1/JXG.Options.grid.gridX uc wide and 1/JXG.Options.grid.gridY uc high. 3135 // this may work perfectly with GeonextReader (#readGeonext, initialization of gridX and gridY) but it 3136 // is absolutely not user friendly when it comes to use it as an API interface. 3137 // i changed this to use gridX and gridY as the actual width and height of the grid cell. for this i 3138 // had to refactor these methods: 3139 // 3140 // DONE JXG.Board.calculateSnapSizes (init p1, p2) 3141 // DONE JXG.GeonextReader.readGeonext (init gridX, gridY) 3142 // 3143 3144 board.options.grid.hasGrid = true; 3145 3146 // fix_grid: adding integer function to calculation of start and end values, and adding to calculation of start and end values below 3147 // To allow this: 3148 // (axes on the outside, min value of grid = 0.25) 3149 // 3150 // | | | | 3151 // 1.5 -+----+---------+----------+----- 3152 // | | | | 3153 // | | | | 3154 // | | | | 3155 // 1 -+----+---------+----------+----- 3156 // | | | | 3157 // | | | | 3158 // | | | | 3159 // 0.5 -+----+---------+----------+----- 3160 // | | | | 3161 // +----+---------+----------+----- 3162 // | | | 3163 // 0.5 1 1.5 3164 // 3165 // fix_grid: these lines disabled: 3166 // topLeft.setCoordinates(Const.COORDS_BY_USER, [Math.ceil(topLeft.usrCoords[1] / gridX) * gridX, Math.floor(topLeft.usrCoords[2] / gridY) * gridY]); 3167 // bottomRight.setCoordinates(Const.COORDS_BY_USER, [Math.floor(bottomRight.usrCoords[1] / gridX) * gridX, Math.ceil(bottomRight.usrCoords[2] / gridY) * gridY]); 3168 3169 c.dataX = []; 3170 c.dataY = []; 3171 3172 // Sometimes the bounding box is used to invert the axis. We have to take this into account here. 3173 // fix_grid: adding integer function to calculation of start and end values 3174 start = Math.floor(topLeft.usrCoords[2] / gridY) * gridY; 3175 end = Math.ceil(bottomRight.usrCoords[2] / gridY) * gridY; 3176 3177 if (topLeft.usrCoords[2] < bottomRight.usrCoords[2]) { 3178 start = Math.ceil(bottomRight.usrCoords[2] / gridY) * gridY; // bottomRight.usrCoords[2]; 3179 end = Math.floor(topLeft.usrCoords[2] / gridY) * gridY; 3180 } 3181 3182 // Start with the horizontal grid: 3183 is_finite = (!isFinite(start) || !isFinite(end) || Math.abs(end) > Math.abs(gridY * max_lines)) ? false : true; 3184 for (i = start; is_finite && i > end - gridY; i -= gridY) { 3185 c.dataX.push(topLeft.usrCoords[1], bottomRight.usrCoords[1], NaN); 3186 c.dataY.push(i, i, NaN); 3187 } 3188 3189 // fix_grid: adding integer function to calculation of start and end values 3190 start = Math.ceil(topLeft.usrCoords[1] / gridX) * gridX; 3191 end = Math.floor(bottomRight.usrCoords[1] / gridX) * gridX; 3192 3193 if (topLeft.usrCoords[1] > bottomRight.usrCoords[1]) { 3194 start = Math.floor(bottomRight.usrCoords[1] / gridX) * gridX; 3195 end = Math.ceil(topLeft.usrCoords[1] / gridX) * gridX; 3196 } 3197 3198 // Build vertical grid 3199 is_finite = (!isFinite(start) || !isFinite(end) || Math.abs(end) > Math.abs(gridX * max_lines)) ? false : true; 3200 for (i = start; is_finite && i < end + gridX; i += gridX) { 3201 c.dataX.push(i, i, NaN); 3202 c.dataY.push(topLeft.usrCoords[2], bottomRight.usrCoords[2], NaN); 3203 } 3204 }; 3205 3206 // We don't care about highlighting so we turn it off completely to save a lot of 3207 // time on every mouse move 3208 c.hasPoint = function () { 3209 return false; 3210 }; 3211 3212 board.grids.push(c); 3213 3214 return c; 3215 }; 3216 3217 /** 3218 * @class Creates an area indicating the solution of a linear inequality or an inequality 3219 * of a function graph, i.e. an inequality of type y <= f(x). 3220 * @pseudo 3221 * @description Display the solution set of a linear inequality (less than or equal to). 3222 * To be precise, the solution set of the inequality <i>y <= b/a * x + c/a</i> is shown. 3223 * In case <i>a = 0</i>, that is if the equation of the line is <i>bx + c = 0</i>, 3224 * the area of the inequality <i>bx + c <= 0</i> is shown. 3225 * <p> 3226 * For function graphs the area below the function graph is filled, i.e. the 3227 * area of the inequality y <= f(x). 3228 * With the attribute inverse:true the area of the inequality y >= f(x) is filled. 3229 * 3230 * @param {JXG.Line} l The area drawn will be the area below this line. With the attribute 3231 * inverse:true, the inequality 'greater than or equal to' is shown. 3232 * @constructor 3233 * @name Inequality 3234 * @type JXG.Curve 3235 * @augments JXG.Curve 3236 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 3237 * @example 3238 * var p = board.create('point', [1, 3]), 3239 * q = board.create('point', [-2, -4]), 3240 * l = board.create('line', [p, q]), 3241 * ineq = board.create('inequality', [l]); 3242 * ineq = board.create('inequality', [l]); 3243 * </pre><div class="jxgbox" id="JXG2b703006-fd98-11e1-b79e-ef9e591c002e" style="width: 400px; height: 400px;"></div> 3244 * <script type="text/javascript"> 3245 * (function () { 3246 * var board = JXG.JSXGraph.initBoard('JXG2b703006-fd98-11e1-b79e-ef9e591c002e', {boundingbox:[-4, 6, 10, -6], axis: false, grid: false, keepaspectratio: true}), 3247 * p = board.create('point', [1, 3]), 3248 * q = board.create('point', [-2, -4]), 3249 * l = board.create('line', [p, q]), 3250 * ineq = board.create('inequality', [l]); 3251 * })(); 3252 * </script><pre> 3253 * 3254 * @example 3255 * // Plot the inequality 3256 * // y >= 2/3 x + 1 3257 * // or 3258 * // 0 >= -3y + 2x +1 3259 * var l = board.create('line', [1, 2, -3]), 3260 * ineq = board.create('inequality', [l], {inverse:true}); 3261 * </pre><div class="jxgbox" id="JXG1ded3812-2da4-4323-abaf-1db4bad1bfbd" style="width: 400px; height: 400px;"></div> 3262 * <script type="text/javascript"> 3263 * (function () { 3264 * var board = JXG.JSXGraph.initBoard('JXG1ded3812-2da4-4323-abaf-1db4bad1bfbd', {boundingbox:[-4, 6, 10, -6], axis: false, grid: false, keepaspectratio: true}), 3265 * l = board.create('line', [1, 2, -3]), 3266 * ineq = board.create('inequality', [l], {inverse:true}); 3267 * })(); 3268 * </script><pre> 3269 * 3270 * @example 3271 * var f = board.create('functiongraph', ['sin(x)', -2*Math.PI, 2*Math.PI]); 3272 * 3273 * var ineq_lower = board.create('inequality', [f]); 3274 * var ineq_greater = board.create('inequality', [f], {inverse: true, fillColor: 'yellow'}); 3275 * 3276 * 3277 * </pre><div id="JXGdb68c574-414c-11e8-839a-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 3278 * <script type="text/javascript"> 3279 * (function() { 3280 * var board = JXG.JSXGraph.initBoard('JXGdb68c574-414c-11e8-839a-901b0e1b8723', 3281 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 3282 * var f = board.create('functiongraph', ['sin(x)', -2*Math.PI, 2*Math.PI]); 3283 * 3284 * var ineq_lower = board.create('inequality', [f]); 3285 * var ineq_greater = board.create('inequality', [f], {inverse: true, fillColor: 'yellow'}); 3286 * 3287 * 3288 * })(); 3289 * 3290 * </script><pre> 3291 * 3292 */ 3293 JXG.createInequality = function (board, parents, attributes) { 3294 var f, a, attr; 3295 3296 attr = Type.copyAttributes(attributes, board.options, "inequality"); 3297 if (parents[0].elementClass === Const.OBJECT_CLASS_LINE) { 3298 a = board.create("curve", [[], []], attr); 3299 a.hasPoint = function () { 3300 return false; 3301 }; 3302 3303 /** 3304 * @class 3305 * @ignore 3306 */ 3307 a.updateDataArray = function () { 3308 var i1, 3309 i2, 3310 // This will be the height of the area. We mustn't rely upon the board height because if we pan the view 3311 // such that the line is not visible anymore, the borders of the area will get visible in some cases. 3312 h, 3313 bb = board.getBoundingBox(), 3314 inverse = Type.evaluate(this.visProp.inverse), 3315 factor = inverse ? -1 : 1, 3316 expansion = 1.5, 3317 w = expansion * Math.max(bb[2] - bb[0], bb[1] - bb[3]), 3318 // Fake a point (for Math.Geometry.perpendicular) 3319 // contains centroid of the board 3320 dp = { 3321 coords: { 3322 usrCoords: [1, (bb[0] + bb[2]) * 0.5, inverse ? bb[1] : bb[3]] 3323 } 3324 }, 3325 slope1 = parents[0].stdform.slice(1), 3326 slope2 = slope1; 3327 3328 // Calculate the area height as 3329 // expansion times the distance of the line to the 3330 // point in the middle of the top/bottom border. 3331 h = 3332 expansion * 3333 Math.max( 3334 Geometry.perpendicular(parents[0], dp, board)[0].distance( 3335 Const.COORDS_BY_USER, 3336 dp.coords 3337 ), 3338 w 3339 ); 3340 h *= factor; 3341 3342 // reuse dp 3343 dp = { 3344 coords: { 3345 usrCoords: [1, (bb[0] + bb[2]) * 0.5, (bb[1] + bb[3]) * 0.5] 3346 } 3347 }; 3348 3349 // If dp is on the line, Geometry.perpendicular will return a point not on the line. 3350 // Since this somewhat odd behavior of Geometry.perpendicular is needed in GEONExT, 3351 // it is circumvented here. 3352 if ( 3353 Math.abs(Mat.innerProduct(dp.coords.usrCoords, parents[0].stdform, 3)) >= 3354 Mat.eps 3355 ) { 3356 dp = Geometry.perpendicular(parents[0], dp, board)[0].usrCoords; 3357 } else { 3358 dp = dp.coords.usrCoords; 3359 } 3360 i1 = [1, dp[1] + slope1[1] * w, dp[2] - slope1[0] * w]; 3361 i2 = [1, dp[1] - slope2[1] * w, dp[2] + slope2[0] * w]; 3362 3363 // One of the vectors based in i1 and orthogonal to the parent line has the direction d1 = (slope1, -1) 3364 // We will go from i1 to i1 + h*d1, from there to i2 + h*d2 (with d2 calculated equivalent to d1) and 3365 // end up in i2. 3366 this.dataX = [i1[1], i1[1] + slope1[0] * h, i2[1] + slope2[0] * h, i2[1], i1[1]]; 3367 this.dataY = [i1[2], i1[2] + slope1[1] * h, i2[2] + slope2[1] * h, i2[2], i1[2]]; 3368 }; 3369 } else if ( 3370 parents[0].elementClass === Const.OBJECT_CLASS_CURVE && 3371 parents[0].visProp.curvetype === "functiongraph" 3372 ) { 3373 a = board.create("curve", [[], []], attr); 3374 /** 3375 * @class 3376 * @ignore 3377 */ 3378 a.updateDataArray = function () { 3379 var bbox = this.board.getBoundingBox(), 3380 points = [], 3381 infty, 3382 first, 3383 last, 3384 len, 3385 i, 3386 mi = parents[0].minX(), 3387 ma = parents[0].maxX(), 3388 curve_mi, 3389 curve_ma, 3390 firstx, 3391 lastx, 3392 enlarge = (bbox[1] - bbox[3]) * 0.3, // enlarge the bbox vertically by this amount 3393 inverse = Type.evaluate(this.visProp.inverse); 3394 3395 // inverse == true <=> Fill area with y >= f(x) 3396 infty = inverse ? 1 : 3; // we will use either bbox[1] or bbox[3] below 3397 3398 this.dataX = []; 3399 this.dataY = []; 3400 len = parents[0].points.length; 3401 if (len === 0) { 3402 return; 3403 } 3404 3405 bbox[1] += enlarge; 3406 bbox[3] -= enlarge; 3407 3408 last = -1; 3409 while (last < len - 1) { 3410 // Find the first point with real coordinates on this curve segment 3411 for (i = last + 1, first = len; i < len; i++) { 3412 if (parents[0].points[i].isReal()) { 3413 first = i; 3414 break; 3415 } 3416 } 3417 // No real points found -> exit 3418 if (first >= len) { 3419 break; 3420 } 3421 3422 // Find the last point with real coordinates on this curve segment 3423 for (i = first, last = len - 1; i < len - 1; i++) { 3424 if (!parents[0].points[i + 1].isReal()) { 3425 last = i; 3426 break; 3427 } 3428 } 3429 3430 firstx = parents[0].points[first].usrCoords[1]; 3431 lastx = parents[0].points[last].usrCoords[1]; 3432 3433 // Restrict the plot interval if the function ends inside of the board 3434 curve_mi = bbox[0] < mi ? mi : bbox[0]; 3435 curve_ma = bbox[2] > ma ? ma : bbox[2]; 3436 3437 // Found NaNs 3438 curve_mi = first === 0 ? curve_mi : Math.max(curve_mi, firstx); 3439 curve_ma = last === len - 1 ? curve_ma : Math.min(curve_ma, lastx); 3440 3441 // First and last relevant x-coordinate of the curve 3442 curve_mi = first === 0 ? mi : firstx; 3443 curve_ma = last === len - 1 ? ma : lastx; 3444 3445 // Copy the curve points 3446 points = []; 3447 3448 points.push([1, curve_mi, bbox[infty]]); 3449 points.push([1, curve_mi, parents[0].points[first].usrCoords[2]]); 3450 for (i = first; i <= last; i++) { 3451 points.push(parents[0].points[i].usrCoords); 3452 } 3453 points.push([1, curve_ma, parents[0].points[last].usrCoords[2]]); 3454 points.push([1, curve_ma, bbox[infty]]); 3455 points.push(points[0]); 3456 3457 for (i = 0; i < points.length; i++) { 3458 this.dataX.push(points[i][1]); 3459 this.dataY.push(points[i][2]); 3460 } 3461 3462 if (last < len - 1) { 3463 this.dataX.push(NaN); 3464 this.dataY.push(NaN); 3465 } 3466 } 3467 }; 3468 3469 // Previous code: 3470 /** 3471 * @class 3472 * @ignore 3473 */ 3474 a.hasPoint = function () { 3475 return false; 3476 }; 3477 } else { 3478 // Not yet practical? 3479 f = Type.createFunction(parents[0]); 3480 a.addParentsFromJCFunctions([f]); 3481 3482 if (!Type.exists(f)) { 3483 throw new Error( 3484 "JSXGraph: Can't create area with the given parents." + 3485 "\nPossible parent types: [line], [function]" 3486 ); 3487 } 3488 } 3489 3490 a.addParents(parents[0]); 3491 return a; 3492 }; 3493 3494 JXG.registerElement("arrowparallel", JXG.createArrowParallel); 3495 JXG.registerElement("bisector", JXG.createBisector); 3496 JXG.registerElement("bisectorlines", JXG.createAngularBisectorsOfTwoLines); 3497 JXG.registerElement("msector", JXG.createMsector); 3498 JXG.registerElement("circumcircle", JXG.createCircumcircle); 3499 JXG.registerElement("circumcirclemidpoint", JXG.createCircumcenter); 3500 JXG.registerElement("circumcenter", JXG.createCircumcenter); 3501 JXG.registerElement("incenter", JXG.createIncenter); 3502 JXG.registerElement("incircle", JXG.createIncircle); 3503 JXG.registerElement("integral", JXG.createIntegral); 3504 JXG.registerElement("midpoint", JXG.createMidpoint); 3505 JXG.registerElement("mirrorelement", JXG.createMirrorElement); 3506 JXG.registerElement("mirrorpoint", JXG.createMirrorPoint); 3507 JXG.registerElement("normal", JXG.createNormal); 3508 JXG.registerElement("orthogonalprojection", JXG.createOrthogonalProjection); 3509 JXG.registerElement("parallel", JXG.createParallel); 3510 JXG.registerElement("parallelpoint", JXG.createParallelPoint); 3511 JXG.registerElement("perpendicular", JXG.createPerpendicular); 3512 JXG.registerElement("perpendicularpoint", JXG.createPerpendicularPoint); 3513 JXG.registerElement("perpendicularsegment", JXG.createPerpendicularSegment); 3514 JXG.registerElement("reflection", JXG.createReflection); 3515 JXG.registerElement("grid", JXG.createGrid); 3516 JXG.registerElement("inequality", JXG.createInequality); 3517 3518 // export default { 3519 // createArrowParallel: JXG.createArrowParallel, 3520 // createBisector: JXG.createBisector, 3521 // createAngularBisectorOfTwoLines: JXG.createAngularBisectorsOfTwoLines, 3522 // createCircumcircle: JXG.createCircumcircle, 3523 // createCircumcenter: JXG.createCircumcenter, 3524 // createIncenter: JXG.createIncenter, 3525 // createIncircle: JXG.createIncircle, 3526 // createIntegral: JXG.createIntegral, 3527 // createMidpoint: JXG.createMidpoint, 3528 // createMirrorElement: JXG.createMirrorElement, 3529 // createMirrorPoint: JXG.createMirrorPoint, 3530 // createNormal: JXG.createNormal, 3531 // createOrthogonalProjection: JXG.createOrthogonalProjection, 3532 // createParallel: JXG.createParallel, 3533 // createParallelPoint: JXG.createParallelPoint, 3534 // createPerpendicular: JXG.createPerpendicular, 3535 // createPerpendicularPoint: JXG.createPerpendicularPoint, 3536 // createPerpendicularSegmen: JXG.createPerpendicularSegment, 3537 // createReflection: JXG.createReflection, 3538 // createGrid: JXG.createGrid, 3539 // createInequality: JXG.createInequality 3540 // }; 3541