      1 /*
      2     Copyright 2018-2025
      3         Alfred Wassermann,
      4         Tigran Saluev
      5
      6     This file is part of JSXGraph.
      7
      8     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
      9
     10     You can redistribute it and/or modify it under the terms of the
     11
     12       * GNU Lesser General Public License as published by
     13         the Free Software Foundation, either version 3 of the License, or
     14         (at your option) any later version
     15       OR
     16       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
     17
     18     JSXGraph is distributed in the hope that it will be useful,
     19     but WITHOUT ANY WARRANTY; without even the implied warranty of
     20     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     21     GNU Lesser General Public License for more details.
     22
     23     You should have received a copy of the GNU Lesser General Public License and
     24     the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/>
     25     and <https://opensource.org/licenses/MIT/>.
     26  */
     27
     28 /*global JXG: true, define: true*/
     29 /*jslint nomen: true, plusplus: true*/
     30
     31 /**
     32  * @fileoverview In this file the Comb element is defined.
     33  */
     34
     35 import JXG from "../jxg.js";
     36 import Type from "../utils/type.js";
     37
     38 /**
     39  * @class A marker to display domains of inequalities.
     40  * The comb element is defined by two points.
     41  * @pseudo
     42  * @name Comb
     43  * @augments JXG.Curve
     44  * @constructor
     45  * @type JXG.Curve
     46  * @throws {Error} If the element cannot be constructed with the given parent
     47  *  objects an exception is thrown.
     48  * Parameter options:
     49  * @param {JXG.Point,array,function_JXG.Point,array,function} point1,point2 Parent elements
     50  * can be two elements either of type {@link JXG.Point} or array of
     51  * numbers describing the coordinates of a point. In the latter case the point
     52  * will be constructed automatically as a fixed invisible point.
     53  * It is possible to provide a function returning an array or a point,
     54  * instead of providing an array or a point.
     55  * @example
     56  * // Create a simple horizontal comb with invisible endpoints
     57  * var c = board.create('comb', [[1, 0], [3, 0]]);
     58  *
     59  * </pre><div class="jxgbox" id="JXG951ccb6a-52bc-4dc2-80e9-43db064f0f1b" style="width: 300px; height: 300px;"></div>
     60  * <script type="text/javascript">
     61  * (function () {
     62  *   var board = JXG.JSXGraph.initBoard('JXG951ccb6a-52bc-4dc2-80e9-43db064f0f1b', {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright: false, shownavigation: false}),
     63  *     c = board.create('comb', [[1, 0], [3, 0]]);
     64  * })();
     65  * </script><pre>
     66  *
     67  * @example
     68  * var p1 = board.create('glider', [-3, 0, board.defaultAxes.x]);
     69  * var p2 = board.create('glider', [-1, 0, board.defaultAxes.x]);
     70  * var c1 = board.create('comb', [p1, p2], {width: 0.2, frequency: 0.1, angle: Math.PI / 4});
     71  *
     72  * </pre><div id="JXG04186fd2-6340-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
     73  * <script type="text/javascript">
     74  *     (function() {
     75  *         var board = JXG.JSXGraph.initBoard('JXG04186fd2-6340-11e8-9fb9-901b0e1b8723',
     76  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     77  *     var p1 = board.create('glider', [-3, 0, board.defaultAxes.x]);
     78  *     var p2 = board.create('glider', [-1, 0, board.defaultAxes.x]);
     79  *     var c1 = board.create('comb', [p1, p2], {width: 0.2, frequency: 0.1, angle: Math.PI / 4});
     80  *
     81  *     })();
     82  *
     83  * </script><pre>
     84  *
     85  * @example
     86  * var s = board.create('slider', [[1,3], [4,3], [0.1, 0.3, 0.8]]);
     87  * var p1 = board.create('glider', [-3, 0, board.defaultAxes.x]);
     88  * var p2 = board.create('glider', [-1, 0, board.defaultAxes.x]);
     89  * var c1 = board.create('comb', [p1, p2], {
     90  *     width: function(){ return 4*s.Value(); },
     91  *     reverse: function(){ return (s.Value()<0.5) ? false : true; },
     92  *     frequency: function(){ return s.Value(); },
     93  *     angle: function(){ return s.Value() * Math.PI / 2; },
     94  *     curve: {
     95  *         strokeColor: 'red'
     96  *     }
     97  * });
     98  *
     99  * </pre><div id="JXG6eb1bcd1-407e-4f13-8f0c-45ef39a0cfb3" class="jxgbox" style="width: 300px; height: 300px;"></div>
    100  * <script type="text/javascript">
    101  *     (function() {
    102  *         var board = JXG.JSXGraph.initBoard('JXG6eb1bcd1-407e-4f13-8f0c-45ef39a0cfb3',
    103  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    104  *     var s = board.create('slider', [[1,3], [4,3], [0.1, 0.3, 0.8]]);
    105  *     var p1 = board.create('glider', [-3, 0, board.defaultAxes.x]);
    106  *     var p2 = board.create('glider', [-1, 0, board.defaultAxes.x]);
    107  *     var c1 = board.create('comb', [p1, p2], {
    108  *         width: function(){ return 4*s.Value(); },
    109  *         reverse: function(){ return (s.Value()<0.5) ? false : true; },
    110  *         frequency: function(){ return s.Value(); },
    111  *         angle: function(){ return s.Value() * Math.PI / 2; },
    112  *         curve: {
    113  *             strokeColor: 'red'
    114  *         }
    115  *     });
    116  *
    117  *     })();
    118  *
    119  * </script><pre>
    120  *
    121  */
    122 JXG.createComb = function (board, parents, attributes) {
    123     var p1, p2, c, attr, parent_types;
    124     //ds, angle, width, p;
    125
    126     if (parents.length === 2) {
    127         // point 1 given by coordinates
    128         if (Type.isArray(parents[0]) && parents[0].length > 1) {
    129             attr = Type.copyAttributes(attributes, board.options, "comb", "point1");
    130             p1 = board.create("point", parents[0], attr);
    131         } else if (Type.isString(parents[0]) || Type.isPoint(parents[0])) {
    132             p1 = board.select(parents[0]);
    133         } else if (Type.isFunction(parents[0]) && Type.isPoint(parents[0]())) {
    134             p1 = parents[0]();
    135         } else if (
    136             Type.isFunction(parents[0]) &&
    137             parents[0]().length &&
    138             parents[0]().length >= 2
    139         ) {
    140             attr = Type.copyAttributes(attributes, board.options, "comb", "point1");
    141             p1 = JXG.createPoint(board, parents[0](), attr);
    142         } else {
    143             throw new Error(
    144                 "JSXGraph: Can't create comb with parent types '" +
    145                     typeof parents[0] +
    146                     "' and '" +
    147                     typeof parents[1] +
    148                     "'." +
    149                     "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]]"
    150             );
    151         }
    152
    153         // point 2 given by coordinates
    154         if (Type.isArray(parents[1]) && parents[1].length > 1) {
    155             attr = Type.copyAttributes(attributes, board.options, "comb", "point2");
    156             p2 = board.create("point", parents[1], attr);
    157         } else if (Type.isString(parents[1]) || Type.isPoint(parents[1])) {
    158             p2 = board.select(parents[1]);
    159         } else if (Type.isFunction(parents[1]) && Type.isPoint(parents[1]())) {
    160             p2 = parents[1]();
    161         } else if (
    162             Type.isFunction(parents[1]) &&
    163             parents[1]().length &&
    164             parents[1]().length >= 2
    165         ) {
    166             attr = Type.copyAttributes(attributes, board.options, "comb", "point2");
    167             p2 = JXG.createPoint(board, parents[1](), attr);
    168         } else {
    169             throw new Error(
    170                 "JSXGraph: Can't create comb with parent types '" +
    171                     typeof parents[0] +
    172                     "' and '" +
    173                     typeof parents[1] +
    174                     "'." +
    175                     "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]]"
    176             );
    177         }
    178     } else {
    179         parent_types = parents.map(function (parent) {
    180             return "'" + typeof parent + "'";
    181         });
    182         throw new Error(
    183             "JSXGraph: Can't create comb with parent types " +
    184                 parent_types.join(", ") +
    185                 "." +
    186                 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]]"
    187         );
    188     }
    189
    190     attr = Type.copyAttributes(attributes, board.options, 'comb');
    191     // Type.merge(attr, Type.copyAttributes(attributes, board.options, 'comb', 'curve'));
    192      c = board.create('curve', [[0], [0]], attr);
    193
    194     /**
    195      * @class
    196      * @ignore
    197      */
    198     c.updateDataArray = function () {
    199         var s = 0,
    200             max_s = p1.Dist(p2),
    201             cs, sn, dx, dy, x, y, f,
    202             p1_inner = p1,
    203             p2_inner = p2,
    204             ds, angle, width;
    205
    206         ds = c.evalVisProp('frequency');
    207         angle = -c.evalVisProp('angle');
    208         width = c.evalVisProp('width');
    209         if (c.evalVisProp('reverse')) {
    210             p1_inner = p2;
    211             p2_inner = p1;
    212             angle = -angle;
    213         }
    214         cs = Math.cos(angle);
    215         sn = Math.sin(angle);
    216         dx = (p2_inner.X() - p1_inner.X()) / max_s;
    217         dy = (p2_inner.Y() - p1_inner.Y()) / max_s;
    218
    219         // But instead of lifting by sin(angle), we want lifting by width.
    220         cs *= width / Math.abs(sn);
    221         sn *= width / Math.abs(sn);
    222
    223         this.dataX = [];
    224         this.dataY = [];
    225         // TODO Handle infinite bounds?
    226         while (s < max_s) {
    227             x = p1_inner.X() + dx * s;
    228             y = p1_inner.Y() + dy * s;
    229
    230             // We may need to cut the last piece of a comb.
    231             f = Math.min(cs, max_s - s) / Math.abs(cs);
    232             sn *= f;
    233             cs *= f;
    234
    235             this.dataX.push(x);
    236             this.dataY.push(y);
    237
    238             this.dataX.push(x + dx * cs + dy * sn);
    239             this.dataY.push(y - dx * sn + dy * cs);
    240
    241             this.dataX.push(NaN); // Force a jump
    242             this.dataY.push(NaN);
    243             s += ds;
    244         }
    245     };
    246
    247     return c;
    248 };
    249
    250 JXG.registerElement("comb", JXG.createComb);
    251
    252 // export default {
    253 //     createComb: JXG.createComb
    254 // };
    255