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