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 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