1 /*
  2     Copyright 2008-2026
  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 Example file for a triangle implemented as a extension to JSXGraph.
 37  */
 38 
 39 import JXG from "../jxg.js";
 40 import Type from "../utils/type.js";
 41 import Const from "../base/constants.js";
 42 import Polygon from "../base/polygon.js";
 43 
 44 var priv = {
 45     removeSlopeTriangle: function () {
 46         Polygon.prototype.remove.call(this);
 47 
 48         this.board.removeObject(this.toppoint);
 49         this.board.removeObject(this.glider);
 50 
 51         this.board.removeObject(this.baseline);
 52         this.board.removeObject(this.basepoint);
 53 
 54         this.board.removeObject(this.label);
 55 
 56         if (this._isPrivateTangent) {
 57             this.board.removeObject(this.tangent);
 58         }
 59     },
 60     Slope: function () {
 61         return this.tangent.getSlope();
 62     },
 63     getAngle: function (unit) {
 64         return this.tangent.getAngle(unit);
 65     },
 66     DeltaX: function () {
 67         return this.borderHorizontal.Direction()[0];
 68     },
 69     DeltaY: function () {
 70         return this.borderVertical.Direction()[1];
 71     },
 72     Direction: function () {
 73         return this.tangent.Direction();
 74     }
 75 };
 76 
 77 /**
 78  * @class Slope triangle to visualize the slope of a tangent to a curve, circle or line.
 79  * @pseudo
 80  * @name Slopetriangle
 81  * @augments JXG.Line
 82  * @constructor
 83  * @type JXG.Polygon
 84  * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
 85  * Parameter options:
 86  * @param {JXG.Line} t A tangent based on a glider on some object, e.g. curve, circle, line or turtle.
 87  * @param {JXG.Line_JXG.Point} li, p A line and a point on that line.
 88  *  The user has to take care that the point is a member of the line.
 89  * @example
 90  * // Create a slopetriangle on a tangent
 91  * var f = board.create('plot', ['sin(x)']),
 92  *     g = board.create('glider', [1, 2, f]),
 93  *     t = board.create('tangent', [g]),
 94  *
 95  *     st = board.create('slopetriangle', [t]);
 96  *
 97  * </pre><div class="jxgbox" id="JXG951ccb6a-52bc-4dc2-80e9-43db064f0f1b" style="width: 300px; height: 300px;"></div>
 98  * <script type="text/javascript">
 99  * (function () {
100  *   var board = JXG.JSXGraph.initBoard('JXG951ccb6a-52bc-4dc2-80e9-43db064f0f1b', {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright: false, shownavigation: false}),
101  *     f = board.create('plot', ['sin(x)']),
102  *     g = board.create('glider', [1, 2, f]),
103  *     t = board.create('tangent', [g]),
104  *
105  *     st = board.create('slopetriangle', [t]);
106  * })();
107  * </script><pre>
108  *
109  * @example
110  * // Create a on a line and a point on that line
111  * var p1 = board.create('point', [-2, 3]),
112  *     p2 = board.create('point', [2, -3]),
113  *     li = board.create('line', [p1, p2]),
114  *     p = board.create('glider', [0, 0, li]),
115  *
116  *     st = board.create('slopetriangle', [li, p]);
117  *
118  * </pre><div class="jxgbox" id="JXGb52f451c-22cf-4677-852a-0bb9d764ee95" style="width: 300px; height: 300px;"></div>
119  * <script type="text/javascript">
120  * (function () {
121  *   var board = JXG.JSXGraph.initBoard('JXGb52f451c-22cf-4677-852a-0bb9d764ee95', {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright: false, shownavigation: false}),
122  *     p1 = board.create('point', [-2, 3]),
123  *     p2 = board.create('point', [2, -3]),
124  *     li = board.create('line', [p1, p2]),
125  *     p = board.create('glider', [0, 0, li]),
126  *
127  *     st = board.create('slopetriangle', [li, p]);
128  * })();
129  * </script><pre>
130  */
131 JXG.createSlopeTriangle = function (board, parents, attributes) {
132     var el, tangent, tglide, glider,
133         toppoint, baseline, basepoint,
134         label, attr,
135         isPrivateTangent = false;
136 
137     if (parents.length === 1 && parents[0].type === Const.OBJECT_TYPE_TANGENT) {
138         tangent = parents[0];
139         tglide = tangent.glider;
140     } else if (parents.length === 1 && parents[0].type === Const.OBJECT_TYPE_GLIDER) {
141         tglide = parents[0];
142         attr = Type.copyAttributes(attributes, board.options, "slopetriangle", 'tangent');
143         tangent = board.create("tangent", [tglide], attr);
144         isPrivateTangent = true;
145     } else if (
146         parents.length === 2 &&
147         parents[0].elementClass === Const.OBJECT_CLASS_LINE &&
148         Type.isPoint(parents[1])
149     ) {
150         tangent = parents[0];
151         tglide = parents[1];
152     } else {
153         throw new Error(
154             "JSXGraph: Can't create slope triangle with parent types '" +
155             typeof parents[0] +
156             "'."
157         );
158     }
159 
160     attr = Type.copyAttributes(attributes, board.options, "slopetriangle", 'basepoint');
161     basepoint = board.create(
162         "point",
163         [
164             function () {
165                 return [tglide.X() + 1, tglide.Y()];
166             }
167         ],
168         attr
169     );
170 
171     attr = Type.copyAttributes(attributes, board.options, "slopetriangle", 'baseline');
172     baseline = board.create("line", [tglide, basepoint], attr);
173 
174     attr = Type.copyAttributes(attributes, board.options, "slopetriangle", 'glider');
175     glider = board.create("glider", [tglide.X() + 1, tglide.Y(), baseline], attr);
176 
177     attr = Type.copyAttributes(attributes, board.options, "slopetriangle", 'toppoint');
178     toppoint = board.create(
179         "point",
180         [
181             function () {
182                 return [
183                     glider.X(),
184                     glider.Y() + (glider.X() - tglide.X()) * tangent.getSlope()
185                 ];
186             }
187         ],
188         attr
189     );
190 
191     attr = Type.copyAttributes(attributes, board.options, 'slopetriangle');
192     // attr.borders = Type.copyAttributes(attr.borders, board.options, "slopetriangle", 'borders');
193     el = board.create("polygon", [tglide, glider, toppoint], attr);
194     el.elType = 'slopetriangle';
195 
196     /**
197      * Returns the slope of the tangent.
198      * @name Slope
199      * @memberOf Slopetriangle.prototype
200      * @function
201      * @returns {Number} slope of the tangent.
202      */
203     el.Slope = priv.Slope;
204 
205     /**
206      * Returns the angle between the tangent and the x-axis.
207      * @name getAngle
208      * @memberOf Slopetriangle.prototype
209      * @function
210      * @param {String} [unit='radians'] Unit of the returned values. Possible units are
211      * <ul>
212      * <li> 'radians' (default): angle value in radians
213      * <li> 'degrees': angle value in degrees
214      * <li> 'semicircle': angle value in radians as a multiple of π, e.g. if the angle is 1.5π, 1.5 will be returned.
215      * <li> 'circle': angle value in radians as a multiple of 2π
216      * </ul>
217      * @returns {Number}
218      */
219     el.getAngle = priv.getAngle;
220 
221     /**
222      * Returns δx of the slope triangle, that is the slope of the tangent.
223      * This value is less than 0 if the line points to the left.
224      * @name DeltaX
225      * @memberOf Slopetriangle.prototype
226      * @function
227      * @returns {Number}
228      */
229     el.DeltaX = priv.DeltaX;
230 
231     /**
232      * Returns δy of the slope triangle, that is the slope of the tangent.
233      * This value is less than 0 if the line points to the bottom.
234      * @name DeltaY
235      * @memberOf Slopetriangle.prototype
236      * @function
237      * @returns {Number}
238      */
239     el.DeltaY = priv.DeltaY;
240 
241     /**
242      * Returns the direction of the slope triangle, that is the direction of the tangent.
243      * @name Direction
244      * @memberOf Slopetriangle.prototype
245      * @see Line#Direction
246      * @function
247      * @returns {Number} slope of the tangent.
248      */
249     el.Direction = priv.Direction;
250     el.tangent = tangent;
251     el._isPrivateTangent = isPrivateTangent;
252 
253     el.borderHorizontal = el.borders[0];
254     el.borderVertical = el.borders[1];
255     el.borderParallel = el.borders[2];
256 
257     //el.borderHorizontal.setArrow(false, {type: 2, size: 10});
258     //el.borderVertical.setArrow(false, {type: 2, size: 10});
259     el.borderParallel.setArrow(false, false);
260 
261     attr = Type.copyAttributes(attributes, board.options, "slopetriangle", 'label');
262     //label = board.create("text", [
263     //         function () {
264     //             return glider.X() + 0.1;
265     //         },
266     //         function () {
267     //             return (glider.Y() + toppoint.Y()) * 0.5;
268     //         },
269     //         function () {
270     //             return "";
271     //         }
272     //     ],
273     //     attr
274     // );
275 
276     attr = Type.copyAttributes(attr, board.options, 'label');
277     // Add label to vertical polygon edge
278     attr.isLabel = true;
279     attr.anchor = el.borderVertical;
280     attr.priv = el.borderVertical.visProp.priv;
281     attr.id = el.borderVertical.id + 'Label';
282 
283     label = board.create("text", [0, 0, function () { return ""; }], attr);
284     label.elType = 'label';
285     label.needsUpdate = true;
286     label.dump = false;
287     el.borderVertical.label = label;
288     el.borderVertical.hasLabel = true;
289     el.borderVertical.visProp.withlabel = true;
290 
291     el.borderVertical.slopetriangle = el;
292     el.borderHorizontal.slopetriangle = el;
293     glider.slopetriangle = el;
294     basepoint.slopetriangle = el;
295     baseline.slopetriangle = el;
296     toppoint.slopetriangle = el;
297     label.slopetriangle = el;
298 
299     label.setText(function () {
300         var prefix = '',
301             suffix = '',
302             digits = label.evalVisProp('digits'),
303             val = el.Slope();
304 
305         if (label.evalVisProp('showprefix')) {
306             prefix = label.evalVisProp('prefix');
307         }
308         if (label.evalVisProp('showsuffix')) {
309             suffix = label.evalVisProp('suffix');
310         }
311 
312         if (digits === 'none') {
313             // do nothing
314         } else if (digits === 'auto') {
315             if (label.useLocale()) {
316                 val = label.formatNumberLocale(val);
317             } else {
318                 val = Type.autoDigits(val);
319             }
320         } else {
321             if (label.useLocale()) {
322                 val = label.formatNumberLocale(val, digits);
323             } else {
324                 val = Type.toFixed(val, digits);
325             }
326         }
327 
328         if (Type.isFunction(el.visProp.formatvalue)) {
329             val = el.visProp.formatvalue(el, val);
330         }
331 
332         return prefix + val + suffix;
333     });
334     label.fullUpdate();
335 
336     el.glider = glider;
337     el.basepoint = basepoint;
338     el.baseline = baseline;
339     el.toppoint = toppoint;
340     el.label = label;
341 
342     el.subs = {
343         glider: glider,
344         basePoint: basepoint,
345         baseLine: baseline,
346         topPoint: toppoint,
347         label: label
348     };
349     el.inherits.push(glider, basepoint, baseline, toppoint, label);
350 
351     el.methodMap = JXG.deepCopy(el.methodMap, {
352         tangent: "tangent",
353         glider: "glider",
354         basepoint: "basepoint",
355         baseline: "baseline",
356         toppoint: "toppoint",
357         borderHorizontal: "borderHorizontal",
358         borderVertical: "borderVertical",
359         borderParallel: "borderParallel",
360         label: "label",
361         Value: "Slope",
362         V: "Slope",
363         Slope: "Slope",
364         Angle: "getAngle",
365         getAngle: "getAngle",
366         DeltaX: "DeltaX",
367         DeltaY: "DeltaY",
368         Direction: "Direction"
369     });
370 
371     el.remove = priv.removeSlopeTriangle;
372 
373     return el;
374 };
375 
376 JXG.registerElement("slopetriangle", JXG.createSlopeTriangle);
377 
378 // export default {
379 //     createSlopeTriangle: JXG.createSlopeTriangle
380 // };
381