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