      1 /*
      2     Copyright 2018-2024
      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 comb to display domains of inequalities.
     40  * @pseudo
     41  * @name Comb
     42  * @augments JXG.Curve
     43  * @constructor
     44  * @type JXG.Curve
     45  * @throws {Error} If the element cannot be constructed with the given parent
     46  *  objects an exception is thrown.
     47  * Parameter options:
     48  * @param {JXG.Point,array,function_JXG.Point,array,function} point1,point2 Parent elements
     49  * can be two elements either of type {@link JXG.Point} or array of
     50  * numbers describing the coordinates of a point. In the latter case the point
     51  * will be constructed automatically as a fixed invisible point.
     52  * It is possible to provide a function returning an array or a point,
     53  * instead of providing an array or a point.
     54  * @example
     55  * // Create a simple horizontal comb with invisible endpoints
     56  * var c = board.create('comb', [[1, 0], [3, 0]]);
     57  *
     58  * </pre><div class="jxgbox" id="JXG951ccb6a-52bc-4dc2-80e9-43db064f0f1b" style="width: 300px; height: 300px;"></div>
     59  * <script type="text/javascript">
     60  * (function () {
     61  *   var board = JXG.JSXGraph.initBoard('JXG951ccb6a-52bc-4dc2-80e9-43db064f0f1b', {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright: false, shownavigation: false}),
     62  *     c = board.create('comb', [[1, 0], [3, 0]]);
     63  * })();
     64  * </script><pre>
     65  *
     66  * @example
     67  * var p1 = board.create('glider', [-3, 0, board.defaultAxes.x]);
     68  * var p2 = board.create('glider', [-1, 0, board.defaultAxes.x]);
     69  * var c1 = board.create('comb', [p1, p2], {width: 0.2, frequency: 0.1, angle: Math.PI / 4});
     70  *
     71  * </pre><div id="JXG04186fd2-6340-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
     72  * <script type="text/javascript">
     73  *     (function() {
     74  *         var board = JXG.JSXGraph.initBoard('JXG04186fd2-6340-11e8-9fb9-901b0e1b8723',
     75  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     76  *     var p1 = board.create('glider', [-3, 0, board.defaultAxes.x]);
     77  *     var p2 = board.create('glider', [-1, 0, board.defaultAxes.x]);
     78  *     var c1 = board.create('comb', [p1, p2], {width: 0.2, frequency: 0.1, angle: Math.PI / 4});
     79  *
     80  *     })();
     81  *
     82  * </script><pre>
     83  *
     84  * @example
     85  * var s = board.create('slider', [[1,3], [4,3], [0.1, 0.3, 0.8]]);
     86  * var p1 = board.create('glider', [-3, 0, board.defaultAxes.x]);
     87  * var p2 = board.create('glider', [-1, 0, board.defaultAxes.x]);
     88  * var c1 = board.create('comb', [p1, p2], {
     89  *     width: function(){ return 4*s.Value(); },
     90  *     reverse: function(){ return (s.Value()<0.5) ? false : true; },
     91  *     frequency: function(){ return s.Value(); },
     92  *     angle: function(){ return s.Value() * Math.PI / 2; },
     93  *     curve: {
     94  *         strokeColor: 'red'
     95  *     }
     96  * });
     97  *
     98  * </pre><div id="JXG6eb1bcd1-407e-4f13-8f0c-45ef39a0cfb3" class="jxgbox" style="width: 300px; height: 300px;"></div>
     99  * <script type="text/javascript">
    100  *     (function() {
    101  *         var board = JXG.JSXGraph.initBoard('JXG6eb1bcd1-407e-4f13-8f0c-45ef39a0cfb3',
    102  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    103  *     var s = board.create('slider', [[1,3], [4,3], [0.1, 0.3, 0.8]]);
    104  *     var p1 = board.create('glider', [-3, 0, board.defaultAxes.x]);
    105  *     var p2 = board.create('glider', [-1, 0, board.defaultAxes.x]);
    106  *     var c1 = board.create('comb', [p1, p2], {
    107  *         width: function(){ return 4*s.Value(); },
    108  *         reverse: function(){ return (s.Value()<0.5) ? false : true; },
    109  *         frequency: function(){ return s.Value(); },
    110  *         angle: function(){ return s.Value() * Math.PI / 2; },
    111  *         curve: {
    112  *             strokeColor: 'red'
    113  *         }
    114  *     });
    115  *
    116  *     })();
    117  *
    118  * </script><pre>
    119  *
    120  */
    121 JXG.createComb = function (board, parents, attributes) {
    122     var p1, p2, c, attr, parent_types;
    123     //ds, angle, width, p;
    124
    125     if (parents.length === 2) {
    126         // point 1 given by coordinates
    127         if (Type.isArray(parents[0]) && parents[0].length > 1) {
    128             attr = Type.copyAttributes(attributes, board.options, "comb", "point1");
    129             p1 = board.create("point", parents[0], attr);
    130         } else if (Type.isString(parents[0]) || Type.isPoint(parents[0])) {
    131             p1 = board.select(parents[0]);
    132         } else if (Type.isFunction(parents[0]) && Type.isPoint(parents[0]())) {
    133             p1 = parents[0]();
    134         } else if (
    135             Type.isFunction(parents[0]) &&
    136             parents[0]().length &&
    137             parents[0]().length >= 2
    138         ) {
    139             attr = Type.copyAttributes(attributes, board.options, "comb", "point1");
    140             p1 = JXG.createPoint(board, parents[0](), attr);
    141         } else {
    142             throw new Error(
    143                 "JSXGraph: Can't create comb with parent types '" +
    144                     typeof parents[0] +
    145                     "' and '" +
    146                     typeof parents[1] +
    147                     "'." +
    148                     "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]]"
    149             );
    150         }
    151
    152         // point 2 given by coordinates
    153         if (Type.isArray(parents[1]) && parents[1].length > 1) {
    154             attr = Type.copyAttributes(attributes, board.options, "comb", "point2");
    155             p2 = board.create("point", parents[1], attr);
    156         } else if (Type.isString(parents[1]) || Type.isPoint(parents[1])) {
    157             p2 = board.select(parents[1]);
    158         } else if (Type.isFunction(parents[1]) && Type.isPoint(parents[1]())) {
    159             p2 = parents[1]();
    160         } else if (
    161             Type.isFunction(parents[1]) &&
    162             parents[1]().length &&
    163             parents[1]().length >= 2
    164         ) {
    165             attr = Type.copyAttributes(attributes, board.options, "comb", "point2");
    166             p2 = JXG.createPoint(board, parents[1](), attr);
    167         } else {
    168             throw new Error(
    169                 "JSXGraph: Can't create comb with parent types '" +
    170                     typeof parents[0] +
    171                     "' and '" +
    172                     typeof parents[1] +
    173                     "'." +
    174                     "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]]"
    175             );
    176         }
    177     } else {
    178         parent_types = parents.map(function (parent) {
    179             return "'" + typeof parent + "'";
    180         });
    181         throw new Error(
    182             "JSXGraph: Can't create comb with parent types " +
    183                 parent_types.join(", ") +
    184                 "." +
    185                 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]]"
    186         );
    187     }
    188
    189     attr = Type.copyAttributes(attributes, board.options, 'comb');
    190     // Type.merge(attr, Type.copyAttributes(attributes, board.options, 'comb', 'curve'));
    191      c = board.create('curve', [[0], [0]], attr);
    192
    193     /**
    194      * @class
    195      * @ignore
    196      */
    197     c.updateDataArray = function () {
    198         var s = 0,
    199             max_s = p1.Dist(p2),
    200             cs, sn, dx, dy, x, y, f,
    201             p1_inner = p1,
    202             p2_inner = p2,
    203             ds, angle, width;
    204
    205         ds = Type.evaluate(c.visProp.frequency);
    206         angle = -Type.evaluate(c.visProp.angle);
    207         width = Type.evaluate(c.visProp.width);
    208         if (Type.evaluate(c.visProp.reverse)) {
    209             p1_inner = p2;
    210             p2_inner = p1;
    211             angle = -angle;
    212         }
    213         cs = Math.cos(angle);
    214         sn = Math.sin(angle);
    215         dx = (p2_inner.X() - p1_inner.X()) / max_s;
    216         dy = (p2_inner.Y() - p1_inner.Y()) / max_s;
    217
    218         // But instead of lifting by sin(angle), we want lifting by width.
    219         cs *= width / Math.abs(sn);
    220         sn *= width / Math.abs(sn);
    221
    222         this.dataX = [];
    223         this.dataY = [];
    224         // TODO Handle infinite bounds?
    225         while (s < max_s) {
    226             x = p1_inner.X() + dx * s;
    227             y = p1_inner.Y() + dy * s;
    228
    229             // We may need to cut the last piece of a comb.
    230             f = Math.min(cs, max_s - s) / Math.abs(cs);
    231             sn *= f;
    232             cs *= f;
    233
    234             this.dataX.push(x);
    235             this.dataY.push(y);
    236
    237             this.dataX.push(x + dx * cs + dy * sn);
    238             this.dataY.push(y - dx * sn + dy * cs);
    239
    240             this.dataX.push(NaN); // Force a jump
    241             this.dataY.push(NaN);
    242             s += ds;
    243         }
    244     };
    245
    246     return c;
    247 };
    248
    249 JXG.registerElement("comb", JXG.createComb);
    250
    251 // export default {
    252 //     createComb: JXG.createComb
    253 // };
    254