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 = Type.evaluate(li.label.visProp.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 Measurement element. Under the hood this is a text element which has a method Value. The text to be displayed 198 * is the result of the evaluation of a prefix expression, see {@link JXG.PrefixParser}. 199 * <p> 200 * The purpose of this element is to display values of measurements of geometric objects, like the radius of a circle, 201 * as well as expressions consisting of measurements. 202 * 203 * @pseudo 204 * @name Measurement 205 * @augments Text 206 * @constructor 207 * @type JXG.Text 208 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 209 * @param {Point|Array_Point|Array_Array} x,y,expression 210 * Here, expression is a prefix expression, see {@link JXG.PrefixParser}. 211 * @example 212 * var p1 = board.create('point', [1, 1]); 213 * var p2 = board.create('point', [1, 3]); 214 * var ci1 = board.create('circle', [p1, p2]); 215 * 216 * var m1 = board.create('measurement', [1, -2, ['Area', ci1]], { 217 * visible: true, 218 * prefix: 'area: ', 219 * baseUnit: 'cm' 220 * }); 221 * 222 * var m2 = board.create('measurement', [1, -4, ['Radius', ci1]], { 223 * prefix: 'radius: ', 224 * baseUnit: 'cm' 225 * }); 226 * 227 * </pre><div id="JXG6359237a-79bc-4689-92fc-38d3ebeb769d" class="jxgbox" style="width: 300px; height: 300px;"></div> 228 * <script type="text/javascript"> 229 * (function() { 230 * var board = JXG.JSXGraph.initBoard('JXG6359237a-79bc-4689-92fc-38d3ebeb769d', 231 * {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright: false, shownavigation: false}); 232 * var p1 = board.create('point', [1, 1]); 233 * var p2 = board.create('point', [1, 3]); 234 * var ci1 = board.create('circle', [p1, p2]); 235 * 236 * var m1 = board.create('measurement', [1, -2, ['Area', ci1]], { 237 * visible: true, 238 * prefix: 'area: ', 239 * baseUnit: 'cm' 240 * }); 241 * 242 * var m2 = board.create('measurement', [1, -4, ['Radius', ci1]], { 243 * prefix: 'radius: ', 244 * baseUnit: 'cm' 245 * }); 246 * 247 * })(); 248 * 249 * </script><pre> 250 * 251 * @example 252 * var p1 = board.create('point', [1, 1]); 253 * var p2 = board.create('point', [1, 3]); 254 * var ci1 = board.create('circle', [p1, p2]); 255 * var seg = board.create('segment', [[-2,-3], [-2, 3]], { firstArrow: true, lastArrow: true}); 256 * var sli = board.create('slider', [[-4, 4], [-1.5, 4], [-10, 1, 10]], {name:'a'}); 257 * 258 * var m1 = board.create('measurement', [-6, -2, ['Radius', ci1]], { 259 * prefix: 'm1: ', 260 * baseUnit: 'cm' 261 * }); 262 * 263 * var m2 = board.create('measurement', [-6, -4, ['L', seg]], { 264 * prefix: 'm2: ', 265 * baseUnit: 'cm' 266 * }); 267 * 268 * var m3 = board.create('measurement', [-6, -6, ['V', sli]], { 269 * prefix: 'm3: ', 270 * baseUnit: 'cm', 271 * dim: 1 272 * }); 273 * 274 * var m4 = board.create('measurement', [2, -6, 275 * ['+', ['V', m1], ['V', m2], ['V', m3]] 276 * ], { 277 * prefix: 'm4: ', 278 * baseUnit: 'cm' 279 * }); 280 * 281 * </pre><div id="JXG49903663-6450-401e-b0d9-f025a6677d4a" class="jxgbox" style="width: 300px; height: 300px;"></div> 282 * <script type="text/javascript"> 283 * (function() { 284 * var board = JXG.JSXGraph.initBoard('JXG49903663-6450-401e-b0d9-f025a6677d4a', 285 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 286 * var p1 = board.create('point', [1, 1]); 287 * var p2 = board.create('point', [1, 3]); 288 * var ci1 = board.create('circle', [p1, p2]); 289 * var seg = board.create('segment', [[-2,-3], [-2, 3]], { firstArrow: true, lastArrow: true}); 290 * var sli = board.create('slider', [[-4, 4], [-1.5, 4], [-10, 1, 10]], {name:'a'}); 291 * 292 * var m1 = board.create('measurement', [-6, -2, ['Radius', ci1]], { 293 * prefix: 'm1: ', 294 * baseUnit: 'cm' 295 * }); 296 * 297 * var m2 = board.create('measurement', [-6, -4, ['L', seg]], { 298 * prefix: 'm2: ', 299 * baseUnit: 'cm' 300 * }); 301 * 302 * var m3 = board.create('measurement', [-6, -6, ['V', sli]], { 303 * prefix: 'm3: ', 304 * baseUnit: 'cm', 305 * dim: 1 306 * }); 307 * 308 * var m4 = board.create('measurement', [2, -6, 309 * ['+', ['V', m1], ['V', m2], ['V', m3]] 310 * ], { 311 * prefix: 'm4: ', 312 * baseUnit: 'cm' 313 * }); 314 * 315 * })(); 316 * 317 * </script><pre> 318 * 319 */ 320 JXG.createMeasurement = function (board, parents, attributes) { 321 var el, attr, 322 x, y, term, 323 i; 324 325 attr = Type.copyAttributes(attributes, board.options, "measurement"); 326 327 x = parents[0]; 328 y = parents[1]; 329 term = parents[2]; 330 331 el = board.create("text", [x, y, ''], attr); 332 el.type = Type.OBJECT_TYPE_MEASUREMENT; 333 el.elType = 'measurement'; 334 335 el.Value = function () { 336 return Prefix.parse(term, 'execute'); 337 }; 338 339 el.Dimension = function () { 340 var d = Type.evaluate(el.visProp.dim); 341 342 if (d !== null) { 343 return d; 344 } 345 return Prefix.dimension(term); 346 }; 347 348 el.Unit = function () { 349 var unit = '', 350 units = Type.evaluate(el.visProp.units), 351 dim = el.Dimension(); 352 353 if (Type.isObject(units) && Type.exists(units[dim]) && units[dim] !== false) { 354 unit = Type.evaluate(units[dim]); 355 } else if (Type.isObject(units) && Type.exists(units['dim' + dim]) && units['dim' + dim] !== false) { 356 // In some cases, object keys must not be numbers. This allows key 'dim1' instead of '1'. 357 unit = Type.evaluate(units['dim' + dim]); 358 } else { 359 unit = Type.evaluate(el.visProp.baseunit); 360 361 if (dim === 0) { 362 unit = ''; 363 } else if (dim > 1 && unit !== '') { 364 unit = unit + '^{' + dim + '}'; 365 } 366 } 367 368 return unit; 369 }; 370 371 el.getTerm = function () { 372 return term; 373 }; 374 375 el.getMethod = function () { 376 var method = term[0]; 377 if (method === "V") { 378 method = "Value"; 379 } 380 return method; 381 }; 382 383 el.toPrefix = function () { 384 return Prefix.toPrefix(term); 385 }; 386 387 el.getParents = function () { 388 return Prefix.getParents(term); 389 }; 390 el.addParents(el.getParents()); 391 for (i = 0; i < el.parents.length; i++) { 392 board.select(el.parents[i]).addChild(el); 393 } 394 395 /** 396 * @class 397 * @ignore 398 */ 399 el.setText(function () { 400 var prefix = '', 401 suffix = '', 402 dim = el.Dimension(), 403 digits = Type.evaluate(el.visProp.digits), 404 unit = el.Unit(), 405 val = el.Value(), 406 i; 407 408 if (Type.evaluate(el.visProp.showprefix)) { 409 prefix = el.visProp.formatprefix.apply(el, [Type.evaluate(el.visProp.prefix)]); 410 } 411 if (Type.evaluate(el.visProp.showsuffix)) { 412 suffix = el.visProp.formatsuffix.apply(el, [Type.evaluate(el.visProp.suffix)]); 413 } 414 415 if (Type.isNumber(val)) { 416 if (digits === 'none') { 417 // do nothing 418 } else if (digits === 'auto') { 419 if (el.useLocale()) { 420 val = el.formatNumberLocale(val); 421 } else { 422 val = Type.autoDigits(val); 423 } 424 } else { 425 if (el.useLocale()) { 426 val = el.formatNumberLocale(val, digits); 427 } else { 428 val = Type.toFixed(val, digits); 429 } 430 } 431 } else if (Type.isArray(val)) { 432 for (i = 0; i < val.length; i++) { 433 if (!Type.isNumber(val[i])) { 434 continue; 435 } 436 if (digits === 'none') { 437 // do nothing 438 } else if (digits === 'auto') { 439 if (el.useLocale()) { 440 val[i] = el.formatNumberLocale(val[i]); 441 } else { 442 val[i] = Type.autoDigits(val[i]); 443 } 444 } else { 445 if (el.useLocale()) { 446 val[i] = el.formatNumberLocale(val[i], digits); 447 } else { 448 val[i] = Type.toFixed(val[i], digits); 449 } 450 } 451 } 452 } 453 454 if (dim === 'coords' && Type.isArray(val)) { 455 if (val.length === 2) { 456 val.unshift(undefined); 457 } 458 val = el.visProp.formatcoords.apply(el, [val[1], val[2], val[0]]); 459 } 460 461 if (dim === 'direction' && Type.isArray(val)) { 462 val = el.visProp.formatdirection.apply(el, [val[0], val[1]]); 463 } 464 465 if (Type.isString(dim)) { 466 return prefix + val + suffix; 467 } 468 469 if (isNaN(dim)) { 470 return prefix + 'NaN' + suffix; 471 } 472 473 return prefix + val + unit + suffix; 474 }); 475 476 el.methodMap = Type.deepCopy(el.methodMap, { 477 Value: "Value", 478 Dimension: "Dimension", 479 Unit: "Unit", 480 getTerm: "getTerm", 481 Term: "getTerm", 482 getMethod: "getMethod", 483 Method: "getMethod", 484 getParents: "getParents", 485 Parents: "getParents" 486 }); 487 488 return el; 489 }; 490 491 JXG.registerElement("measurement", JXG.createMeasurement); 492