1 /* 2 Copyright 2008-2024 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Bianca Valentin, 7 Alfred Wassermann, 8 Peter Wilfahrt 9 10 This file is part of JSXGraph. 11 12 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 13 14 You can redistribute it and/or modify it under the terms of the 15 16 * GNU Lesser General Public License as published by 17 the Free Software Foundation, either version 3 of the License, or 18 (at your option) any later version 19 OR 20 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 21 22 JSXGraph is distributed in the hope that it will be useful, 23 but WITHOUT ANY WARRANTY; without even the implied warranty of 24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 GNU Lesser General Public License for more details. 26 27 You should have received a copy of the GNU Lesser General Public License and 28 the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/> 29 and <https://opensource.org/licenses/MIT/>. 30 */ 31 32 /*global JXG: true, define: true*/ 33 /*jslint nomen: true, plusplus: true*/ 34 35 /** 36 * @fileoverview Geometry objects for measurements are defined in this file. This file stores all 37 * style and functional properties that are required to use a tape measure on 38 * a board. 39 */ 40 41 import JXG from "../jxg.js"; 42 import Type from "../utils/type.js"; 43 import GeometryElement from "../base/element.js"; 44 import Prefix from "../parser/prefix.js"; 45 46 /** 47 * @class A tape measure can be used to measure distances between points. 48 * <p> 49 * The two defining points of the tape measure (which is a segment) do not inherit by default the attribute "visible" from 50 * the segment. Otherwise the tape meassure would be inaccessible if the two points coincide and the segment is hidden. 51 * 52 * @pseudo 53 * @name Tapemeasure 54 * @augments Segment 55 * @constructor 56 * @type JXG.Segment 57 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 58 * @param {Array_Array} start,end, The two arrays give the initial position where the tape measure 59 * is drawn on the board. 60 * @example 61 * // Create a tape measure 62 * var p1 = board.create('point', [0,0]); 63 * var p2 = board.create('point', [1,1]); 64 * var p3 = board.create('point', [3,1]); 65 * var tape = board.create('tapemeasure', [[1, 2], [4, 2]], {name:'dist'}); 66 * </pre><div class="jxgbox" id="JXG6d9a2cda-22fe-4cd1-9d94-34283b1bdc01" style="width: 200px; height: 200px;"></div> 67 * <script type="text/javascript"> 68 * (function () { 69 * var board = JXG.JSXGraph.initBoard('JXG6d9a2cda-22fe-4cd1-9d94-34283b1bdc01', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false}); 70 * var p1 = board.create('point', [0,0]); 71 * var p2 = board.create('point', [1,1]); 72 * var p3 = board.create('point', [3,1]); 73 * var tape = board.create('tapemeasure', [[1, 2], [4, 2]], {name:'dist'} ); 74 * })(); 75 * </script><pre> 76 */ 77 JXG.createTapemeasure = function (board, parents, attributes) { 78 var pos0, pos1, attr, withTicks, withText, digits, li, p1, p2, n, ti; 79 80 pos0 = parents[0]; 81 pos1 = parents[1]; 82 83 // start point 84 attr = Type.copyAttributes(attributes, board.options, "tapemeasure", "point1"); 85 p1 = board.create("point", pos0, attr); 86 87 // end point 88 attr = Type.copyAttributes(attributes, board.options, "tapemeasure", "point2"); 89 p2 = board.create("point", pos1, attr); 90 91 p1.setAttribute({ ignoredSnapToPoints: [p2.id] }); 92 p2.setAttribute({ ignoredSnapToPoints: [p1.id] }); 93 94 // tape measure line 95 attr = Type.copyAttributes(attributes, board.options, "tapemeasure"); 96 withTicks = attr.withticks; 97 withText = attr.withlabel; 98 digits = attr.digits; 99 100 if (digits === 2 && attr.precision !== 2) { 101 // Backward compatibility 102 digits = attr.precision; 103 } 104 105 // Below, we will replace the label by the measurement function. 106 if (withText) { 107 attr.withlabel = true; 108 } 109 li = board.create("segment", [p1, p2], attr); 110 // p1, p2 are already added to li.inherits 111 112 if (withText) { 113 if (attributes.name && attributes.name !== "") { 114 n = attributes.name + " = "; 115 } else { 116 n = ""; 117 } 118 li.label.setText(function () { 119 var digits = li.label.evalVisProp('digits'); 120 121 if (li.label.useLocale()) { 122 return n + li.label.formatNumberLocale(p1.Dist(p2), digits); 123 } 124 return n + Type.toFixed(p1.Dist(p2), digits); 125 }); 126 } 127 128 if (withTicks) { 129 attr = Type.copyAttributes(attributes, board.options, "tapemeasure", "ticks"); 130 //ticks = 2; 131 ti = board.create("ticks", [li, 0.1], attr); 132 li.inherits.push(ti); 133 } 134 135 // override the segments's remove method to ensure the removal of all elements 136 /** @ignore */ 137 li.remove = function () { 138 if (withTicks) { 139 li.removeTicks(ti); 140 } 141 142 board.removeObject(p2); 143 board.removeObject(p1); 144 145 GeometryElement.prototype.remove.call(this); 146 }; 147 148 /** 149 * Returns the length of the tape measure. 150 * @name Value 151 * @memberOf Tapemeasure.prototype 152 * @function 153 * @returns {Number} length of tape measure. 154 */ 155 li.Value = function () { 156 return p1.Dist(p2); 157 }; 158 159 p1.dump = false; 160 p2.dump = false; 161 162 li.elType = "tapemeasure"; 163 li.getParents = function () { 164 return [ 165 [p1.X(), p1.Y()], 166 [p2.X(), p2.Y()] 167 ]; 168 }; 169 170 li.subs = { 171 point1: p1, 172 point2: p2 173 }; 174 175 if (withTicks) { 176 ti.dump = false; 177 } 178 179 li.methodMap = JXG.deepCopy(li.methodMap, { 180 Value: "Value" 181 }); 182 183 li.prepareUpdate().update(); 184 if (!board.isSuspendedUpdate) { 185 li.updateVisibility().updateRenderer(); 186 // The point updates are necessary in case of snapToGrid==true 187 li.point1.updateVisibility().updateRenderer(); 188 li.point2.updateVisibility().updateRenderer(); 189 } 190 191 return li; 192 }; 193 194 JXG.registerElement("tapemeasure", JXG.createTapemeasure); 195 196 /** 197 * @class Display measurements of geometric elements and the arithmetic operations of measurements. 198 * Under the hood this is a text element which has a method Value. The text to be displayed 199 * is the result of the evaluation of a prefix expression, see {@link JXG.PrefixParser}. 200 * <p> 201 * The purpose of this element is to display values of measurements of geometric objects, like the radius of a circle, 202 * as well as expressions consisting of measurements. 203 * 204 * @pseudo 205 * @name Measurement 206 * @augments Text 207 * @constructor 208 * @type JXG.Text 209 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 210 * @param {Point|Array_Point|Array_Array} x,y,expression 211 * Here, expression is a prefix expression, see {@link JXG.PrefixParser}. 212 * @example 213 * var p1 = board.create('point', [1, 1]); 214 * var p2 = board.create('point', [1, 3]); 215 * var ci1 = board.create('circle', [p1, p2]); 216 * 217 * var m1 = board.create('measurement', [1, -2, ['Area', ci1]], { 218 * visible: true, 219 * prefix: 'area: ', 220 * baseUnit: 'cm' 221 * }); 222 * 223 * var m2 = board.create('measurement', [1, -4, ['Radius', ci1]], { 224 * prefix: 'radius: ', 225 * baseUnit: 'cm' 226 * }); 227 * 228 * </pre><div id="JXG6359237a-79bc-4689-92fc-38d3ebeb769d" class="jxgbox" style="width: 300px; height: 300px;"></div> 229 * <script type="text/javascript"> 230 * (function() { 231 * var board = JXG.JSXGraph.initBoard('JXG6359237a-79bc-4689-92fc-38d3ebeb769d', 232 * {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright: false, shownavigation: false}); 233 * var p1 = board.create('point', [1, 1]); 234 * var p2 = board.create('point', [1, 3]); 235 * var ci1 = board.create('circle', [p1, p2]); 236 * 237 * var m1 = board.create('measurement', [1, -2, ['Area', ci1]], { 238 * visible: true, 239 * prefix: 'area: ', 240 * baseUnit: 'cm' 241 * }); 242 * 243 * var m2 = board.create('measurement', [1, -4, ['Radius', ci1]], { 244 * prefix: 'radius: ', 245 * baseUnit: 'cm' 246 * }); 247 * 248 * })(); 249 * 250 * </script><pre> 251 * 252 * @example 253 * var p1 = board.create('point', [1, 1]); 254 * var p2 = board.create('point', [1, 3]); 255 * var ci1 = board.create('circle', [p1, p2]); 256 * var seg = board.create('segment', [[-2,-3], [-2, 3]], { firstArrow: true, lastArrow: true}); 257 * var sli = board.create('slider', [[-4, 4], [-1.5, 4], [-10, 1, 10]], {name:'a'}); 258 * 259 * var m1 = board.create('measurement', [-6, -2, ['Radius', ci1]], { 260 * prefix: 'm1: ', 261 * baseUnit: 'cm' 262 * }); 263 * 264 * var m2 = board.create('measurement', [-6, -4, ['L', seg]], { 265 * prefix: 'm2: ', 266 * baseUnit: 'cm' 267 * }); 268 * 269 * var m3 = board.create('measurement', [-6, -6, ['V', sli]], { 270 * prefix: 'm3: ', 271 * baseUnit: 'cm', 272 * dim: 1 273 * }); 274 * 275 * var m4 = board.create('measurement', [2, -6, 276 * ['+', ['V', m1], ['V', m2], ['V', m3]] 277 * ], { 278 * prefix: 'm4: ', 279 * baseUnit: 'cm' 280 * }); 281 * 282 * </pre><div id="JXG49903663-6450-401e-b0d9-f025a6677d4a" class="jxgbox" style="width: 300px; height: 300px;"></div> 283 * <script type="text/javascript"> 284 * (function() { 285 * var board = JXG.JSXGraph.initBoard('JXG49903663-6450-401e-b0d9-f025a6677d4a', 286 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 287 * var p1 = board.create('point', [1, 1]); 288 * var p2 = board.create('point', [1, 3]); 289 * var ci1 = board.create('circle', [p1, p2]); 290 * var seg = board.create('segment', [[-2,-3], [-2, 3]], { firstArrow: true, lastArrow: true}); 291 * var sli = board.create('slider', [[-4, 4], [-1.5, 4], [-10, 1, 10]], {name:'a'}); 292 * 293 * var m1 = board.create('measurement', [-6, -2, ['Radius', ci1]], { 294 * prefix: 'm1: ', 295 * baseUnit: 'cm' 296 * }); 297 * 298 * var m2 = board.create('measurement', [-6, -4, ['L', seg]], { 299 * prefix: 'm2: ', 300 * baseUnit: 'cm' 301 * }); 302 * 303 * var m3 = board.create('measurement', [-6, -6, ['V', sli]], { 304 * prefix: 'm3: ', 305 * baseUnit: 'cm', 306 * dim: 1 307 * }); 308 * 309 * var m4 = board.create('measurement', [2, -6, 310 * ['+', ['V', m1], ['V', m2], ['V', m3]] 311 * ], { 312 * prefix: 'm4: ', 313 * baseUnit: 'cm' 314 * }); 315 * 316 * })(); 317 * 318 * </script><pre> 319 * 320 */ 321 JXG.createMeasurement = function (board, parents, attributes) { 322 var el, attr, 323 x, y, term, 324 i; 325 326 attr = Type.copyAttributes(attributes, board.options, "measurement"); 327 328 x = parents[0]; 329 y = parents[1]; 330 term = parents[2]; 331 332 el = board.create("text", [x, y, ''], attr); 333 el.type = Type.OBJECT_TYPE_MEASUREMENT; 334 el.elType = 'measurement'; 335 336 el.Value = function () { 337 return Prefix.parse(term, 'execute'); 338 }; 339 340 el.Dimension = function () { 341 var d = el.evalVisProp('dim'); 342 343 if (d !== null) { 344 return d; 345 } 346 return Prefix.dimension(term); 347 }; 348 349 el.Unit = function (dimension) { 350 var unit = '', 351 units = el.evalVisProp('units'), 352 dim = dimension ?? el.Dimension(); 353 354 if (Type.isObject(units) && Type.exists(units[dim]) && units[dim] !== false) { 355 unit = el.eval(units[dim]); 356 } else if (Type.isObject(units) && Type.exists(units['dim' + dim]) && units['dim' + dim] !== false) { 357 // In some cases, object keys must not be numbers. This allows key 'dim1' instead of '1'. 358 unit = el.eval(units['dim' + dim]); 359 } else { 360 unit = el.evalVisProp('baseunit'); 361 362 if (dim === 0) { 363 unit = ''; 364 } else if (dim > 1 && unit !== '') { 365 unit = unit + '^{' + dim + '}'; 366 } 367 } 368 369 return unit; 370 }; 371 372 el.getTerm = function () { 373 return term; 374 }; 375 376 el.getMethod = function () { 377 var method = term[0]; 378 if (method === "V") { 379 method = "Value"; 380 } 381 return method; 382 }; 383 384 el.toPrefix = function () { 385 return Prefix.toPrefix(term); 386 }; 387 388 el.getParents = function () { 389 return Prefix.getParents(term); 390 }; 391 el.addParents(el.getParents()); 392 for (i = 0; i < el.parents.length; i++) { 393 board.select(el.parents[i]).addChild(el); 394 } 395 396 /** 397 * @class 398 * @ignore 399 */ 400 el.setText(function () { 401 var prefix = '', 402 suffix = '', 403 dim = el.Dimension(), 404 digits = el.evalVisProp('digits'), 405 unit = el.Unit(), 406 val = el.Value(), 407 i; 408 409 if (el.evalVisProp('showprefix')) { 410 prefix = el.evalVisProp('prefix'); 411 } 412 if (el.evalVisProp('showsuffix')) { 413 suffix = el.evalVisProp('suffix'); 414 } 415 416 if (Type.isNumber(val)) { 417 if (digits === 'none') { 418 // do nothing 419 } else if (digits === 'auto') { 420 if (el.useLocale()) { 421 val = el.formatNumberLocale(val); 422 } else { 423 val = Type.autoDigits(val); 424 } 425 } else { 426 if (el.useLocale()) { 427 val = el.formatNumberLocale(val, digits); 428 } else { 429 val = Type.toFixed(val, digits); 430 } 431 } 432 } else if (Type.isArray(val)) { 433 for (i = 0; i < val.length; i++) { 434 if (!Type.isNumber(val[i])) { 435 continue; 436 } 437 if (digits === 'none') { 438 // do nothing 439 } else if (digits === 'auto') { 440 if (el.useLocale()) { 441 val[i] = el.formatNumberLocale(val[i]); 442 } else { 443 val[i] = Type.autoDigits(val[i]); 444 } 445 } else { 446 if (el.useLocale()) { 447 val[i] = el.formatNumberLocale(val[i], digits); 448 } else { 449 val[i] = Type.toFixed(val[i], digits); 450 } 451 } 452 } 453 } 454 455 if (dim === 'coords' && Type.isArray(val)) { 456 if (val.length === 2) { 457 val.unshift(undefined); 458 } 459 val = el.visProp.formatcoords(el, val[1], val[2], val[0]); 460 } 461 462 if (dim === 'direction' && Type.isArray(val)) { 463 val = el.visProp.formatdirection(el, val[0], val[1]); 464 } 465 466 if (Type.isString(dim)) { 467 return prefix + val + suffix; 468 } 469 470 if (isNaN(dim)) { 471 return prefix + 'NaN' + suffix; 472 } 473 474 return prefix + val + unit + suffix; 475 }); 476 477 el.methodMap = Type.deepCopy(el.methodMap, { 478 Value: "Value", 479 Dimension: "Dimension", 480 Unit: "Unit", 481 getTerm: "getTerm", 482 Term: "getTerm", 483 getMethod: "getMethod", 484 Method: "getMethod", 485 getParents: "getParents", 486 Parents: "getParents" 487 }); 488 489 return el; 490 }; 491 492 JXG.registerElement("measurement", JXG.createMeasurement); 493