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