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 valueFunc, i, 320 dimFunc; 321 322 attr = Type.copyAttributes(attributes, board.options, "measurement"); 323 324 x = parents[0]; 325 y = parents[1]; 326 term = parents[2]; 327 328 el = board.create("text", [x, y, ''], attr); 329 el.type = Type.OBJECT_TYPE_MEASUREMENT; 330 el.elType = 'measurement'; 331 332 valueFunc = function () { 333 return Prefix.parse(term, 'execute'); 334 }; 335 dimFunc = 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.Value = valueFunc; 345 el.Dimension = dimFunc; 346 el.toInfix = function (type) { 347 return Prefix.toInfix(term, type); 348 }; 349 el.toPrefix = function () { 350 return Prefix.toPrefix(term); 351 }; 352 el.getParents = function () { 353 return Prefix.getParents(term); 354 }; 355 el.addParents(el.getParents()); 356 for (i = 0; i < el.parents.length; i++) { 357 board.select(el.parents[i]).addChild(el); 358 } 359 360 /** 361 * @class 362 * @ignore 363 */ 364 el.setText(function () { 365 var prefix = '', 366 suffix = '', 367 dim = el.Dimension(), 368 unit = '', 369 units = Type.evaluate(el.visProp.units), 370 val = el.Value(); 371 372 if(Type.isNumber(val)) { 373 val = val.toFixed(Type.evaluate(el.visProp.digits)); 374 } 375 376 if (Type.evaluate(el.visProp.showprefix)) { 377 prefix = Type.evaluate(el.visProp.prefix); 378 } 379 if (Type.evaluate(el.visProp.showsuffix)) { 380 suffix = Type.evaluate(el.visProp.suffix); 381 } 382 383 if (Type.isString(dim)) { 384 return prefix + val + suffix; 385 } 386 387 if (isNaN(dim)) { 388 return prefix + 'NaN' + suffix; 389 } 390 391 if (Type.isObject(units) && Type.exists(units[dim])) { 392 unit = Type.evaluate(units[dim]); 393 } else if (Type.isObject(units) && Type.exists(units['dim' + dim])) { 394 // In some cases, object keys must not be numbers. This allows key 'dim1' instead of '1'. 395 unit = Type.evaluate(units['dim' + dim]); 396 } else { 397 unit = Type.evaluate(el.visProp.baseunit); 398 399 if (dim === 0) { 400 unit = ''; 401 } else if (dim > 1 && unit !== '') { 402 unit = unit + '^{' + dim + '}'; 403 } 404 } 405 406 if (unit !== '') { 407 unit = ' ' + unit; 408 } 409 410 return prefix + val + unit + suffix; 411 }); 412 413 el.methodMap = Type.deepCopy(el.methodMap, { 414 Value: "Value", 415 Dimension: "Dimension", 416 toPrefix: "toPrefix", 417 getParents: "getParents" 418 }); 419 420 return el; 421 }; 422 423 JXG.registerElement("measurement", JXG.createMeasurement); 424