pimath 0.0.129 → 0.0.130

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Vector module contains everything necessary to handle 2d or 3d vectors.
3
+ * @module Vector
4
+ */
5
+ import {Line} from "./line";
6
+ import {Vector} from "./vector";
7
+ import {Fraction} from "../coefficients/fraction";
8
+
9
+ /**
10
+ * Helper class - a way to identify an object {x: number, y: number}
11
+ */
12
+ class PointXY {
13
+ x: number
14
+ y: number
15
+ }
16
+
17
+ export class Point {
18
+ private _x: Fraction; // 1st component
19
+ private _y: Fraction; // 2nd component
20
+ private _exist: Boolean;
21
+
22
+ constructor(...values: unknown[]) {
23
+ this._x = new Fraction().zero();
24
+ this._y = new Fraction().zero();
25
+
26
+ if (values !== undefined) {
27
+ this.parse(...values);
28
+ }
29
+
30
+ return this
31
+ };
32
+
33
+ // ------------------------------------------
34
+ // Getter and setter
35
+ // ------------------------------------------
36
+ get x(): Fraction {
37
+ return this._x;
38
+ }
39
+
40
+ set x(value: Fraction) {
41
+ this._x = value;
42
+ }
43
+
44
+ get y(): Fraction {
45
+ return this._y;
46
+ }
47
+
48
+ set y(value: Fraction) {
49
+ this._y = value;
50
+ }
51
+
52
+ get tex(): string {
53
+ let pts = [];
54
+
55
+ pts.push(this._x.tex);
56
+ pts.push(this._y.tex);
57
+
58
+ return `\\left(${pts.join(';')}\\right)`
59
+ }
60
+
61
+ get display(): string {
62
+ let pts = [];
63
+
64
+ pts.push(this._x.tex);
65
+ pts.push(this._y.tex);
66
+
67
+ return `(${pts.join(';')})`
68
+ }
69
+
70
+ get asVector(): Vector {
71
+ return new Vector(this.x, this.y)
72
+ }
73
+
74
+ // ------------------------------------------
75
+ // Creation / parsing functions
76
+
77
+ get key(): string {
78
+ return `${this.x.display};${this.y.display}`
79
+ }
80
+
81
+ // ------------------------------------------
82
+ static pmatrix = (a: any, b: any, c?: any): string => {
83
+ if (c === undefined) {
84
+ return `\\begin{pmatrix} ${a.tex ? a.tex : a} \\\\ ${b.tex ? b.tex : b} \\end{pmatrix}`;
85
+ } else {
86
+ return `\\begin{pmatrix} ${a.tex ? a.tex : a} \\\\ ${b.tex ? b.tex : b} \\\\ ${c.tex ? c.tex : c} \\end{pmatrix}`;
87
+ }
88
+ };
89
+
90
+ // ------------------------------------------
91
+ parse = (...values: unknown[]): Point => {
92
+ // Initialize the value.
93
+ this.zero();
94
+
95
+ // Nothing is given
96
+ if (values.length === 0) {
97
+ return this;
98
+ }
99
+
100
+ // One element is given - might be already a point !
101
+ if (values.length === 1) {
102
+ // it's already a point - clone it
103
+ if (values[0] instanceof Point) {
104
+ this._x = values[0].x.clone()
105
+ this._y = values[0].y.clone()
106
+ return this
107
+ }
108
+
109
+ // Value is given as string, comma separated.
110
+ if (typeof values[0] === 'string') {
111
+ let xy = values[0].split(',')
112
+ if (xy.length === 2) {
113
+ this._x = new Fraction(xy[0]).reduce()
114
+ this._y = new Fraction(xy[1]).reduce()
115
+ return this
116
+ }
117
+ }
118
+
119
+ // Value given as an object with {x: value, y: value}
120
+ if (values[0] instanceof PointXY) {
121
+ this._x = new Fraction(values[0].x).reduce()
122
+ this._y = new Fraction(values[0].y).reduce()
123
+ return this
124
+ } else {
125
+ return this.zero()
126
+ }
127
+ }
128
+
129
+ if (values.length === 2) {
130
+ this._x = new Fraction(values[0]).reduce()
131
+ this._y = new Fraction(values[1]).reduce()
132
+ return this
133
+ }
134
+
135
+ return this;
136
+ };
137
+
138
+ clone = (): Point => {
139
+ this._x = this._x.clone()
140
+ this._y = this._y.clone()
141
+
142
+ return this
143
+ }
144
+
145
+ zero = (): Point => {
146
+ this._x = new Fraction(null);
147
+ this._y = new Fraction(null);
148
+ return this;
149
+ }
150
+
151
+ origin = (): Point => {
152
+ this.zero();
153
+ return this;
154
+ }
155
+ // ------------------------------------------
156
+ // Display functions
157
+
158
+ middleOf = (P1: Point, P2: Point): Point => {
159
+ this._x = P1.x.clone().add(P2.x).divide(2);
160
+ this._y = P1.y.clone().add(P2.y).divide(2);
161
+
162
+ return this;
163
+ }
164
+ // ------------------------------------------
165
+ // Mathematical operations
166
+ // ------------------------------------------
167
+
168
+ // ------------------------------------------
169
+ // Vector functions
170
+ // ------------------------------------------
171
+
172
+ // ------------------------------------------
173
+ // Static functions
174
+
175
+ translate = (value: { x: number | Fraction, y: number | Fraction }): Point => {
176
+ this._x = this._x.add(value.x)
177
+ this._y = this._y.add(value.y)
178
+ return this
179
+ }
180
+
181
+ // ------------------------------------------
182
+ texValues = (numberOfDigits: number): string => {
183
+ let pts = [];
184
+
185
+ pts.push(this._x.value.toFixed(numberOfDigits === undefined ? 2 : numberOfDigits));
186
+ pts.push(this._y.value.toFixed(numberOfDigits === undefined ? 2 : numberOfDigits));
187
+
188
+ return `\\left(${pts.join(';')}\\right)`
189
+ }
190
+
191
+ distanceTo = (item: Point | Line): { value: number, fraction: Fraction, tex: string } => {
192
+ let value = 0, fraction = new Fraction(), tex = ''
193
+
194
+ if (item instanceof Line) {
195
+ return item.distanceTo(this)
196
+ } else if (item instanceof Point) {
197
+ let V = new Vector(this, item)
198
+
199
+ value = V.norm
200
+ fraction = V.normSquare.sqrt()
201
+ tex = V.normSquare.isSquare() ? fraction.tex : `\\sqrt{\\frac{ ${V.normSquare.numerator} }{ ${V.normSquare.denominator} }}`
202
+ }
203
+ return {value, fraction, tex}
204
+ }
205
+
206
+ isInListOfPoints = (list: Point[]): boolean => {
207
+ const keyList = list.map(x => x.key)
208
+
209
+ return keyList.includes(this.key)
210
+ }
211
+
212
+ isEqual = (pt: Point): boolean => {
213
+ return this.x.isEqual(pt.x) && this.y.isEqual(pt.y)
214
+ }
215
+ }
@@ -0,0 +1,368 @@
1
+ import {Point} from "./point";
2
+ import {Fraction} from "../coefficients/fraction";
3
+ import {Vector} from "./vector";
4
+ import {Line} from "./line";
5
+ import {Equation} from "../algebra/equation";
6
+
7
+ export interface remarquableLines {
8
+ 'medians': {
9
+ 'A': Line,
10
+ 'B': Line,
11
+ 'C': Line,
12
+ 'intersection': Point
13
+ },
14
+ 'mediators': {
15
+ 'AB': Line,
16
+ 'AC': Line,
17
+ 'BC': Line,
18
+ 'intersection': Point
19
+ },
20
+ 'heights': {
21
+ 'A': Line,
22
+ 'B': Line,
23
+ 'C': Line,
24
+ 'intersection': Point
25
+ },
26
+ 'bisectors': {
27
+ 'A': Line,
28
+ 'B': Line,
29
+ 'C': Line,
30
+ 'intersection': Point
31
+ },
32
+ externalBisectors: {
33
+ 'A': Line,
34
+ 'B': Line,
35
+ 'C': Line,
36
+ 'intersection': Point
37
+ }
38
+ }
39
+
40
+ export class Triangle {
41
+ private _A: Point;
42
+ private _B: Point;
43
+ private _C: Point;
44
+ private _lines: {
45
+ 'AB': Line,
46
+ 'AC': Line,
47
+ 'BC': Line
48
+ };
49
+ private _middles: {
50
+ 'AB': Point,
51
+ 'AC': Point,
52
+ 'BC': Point
53
+ };
54
+ private _remarquables: remarquableLines;
55
+
56
+
57
+ constructor(...values: unknown[]) {
58
+
59
+ if (values.length > 0) {
60
+ this.parse(...values);
61
+ }
62
+ return this;
63
+ }
64
+
65
+ // ------------------------------------------
66
+ // Getter and setters
67
+ // ------------------------------------------
68
+
69
+ get A(): Point {
70
+ return this._A;
71
+ }
72
+
73
+ get B(): Point {
74
+ return this._B;
75
+ }
76
+
77
+ get C(): Point {
78
+ return this._C;
79
+ }
80
+
81
+ get AB(): Vector {
82
+ return this.getSegment('A', 'B');
83
+ }
84
+
85
+ get BA(): Vector {
86
+ return this.getSegment('B', 'A');
87
+ }
88
+
89
+ get BC(): Vector {
90
+ return this.getSegment('B', 'C');
91
+ }
92
+
93
+ get CB(): Vector {
94
+ return this.getSegment('C', 'B');
95
+ }
96
+
97
+ get AC(): Vector {
98
+ return this.getSegment('A', 'C');
99
+ }
100
+
101
+ get CA(): Vector {
102
+ return this.getSegment('C', 'A');
103
+ }
104
+
105
+ get isRectangle(): boolean {
106
+ if (this.AB.isNormalTo(this.BC)) {
107
+ return true;
108
+ }
109
+ if (this.AB.isNormalTo(this.AC)) {
110
+ return true;
111
+ }
112
+ if (this.BC.isNormalTo(this.AC)) {
113
+ return true;
114
+ }
115
+
116
+ return false;
117
+ }
118
+
119
+ get isEquilateral(): boolean {
120
+ return this.AB.normSquare.isEqual(this.BC.normSquare) &&
121
+ this.AB.normSquare.isEqual(this.AC.normSquare);
122
+ }
123
+
124
+ get isIsocele(): boolean {
125
+ return this.AB.normSquare.isEqual(this.BC.normSquare) ||
126
+ this.AB.normSquare.isEqual(this.AC.normSquare) ||
127
+ this.BC.normSquare.isEqual(this.AC.normSquare)
128
+ }
129
+
130
+ get lines(): { 'AB': Line, 'BC': Line, 'AC': Line } {
131
+ return this._lines;
132
+ }
133
+
134
+ get remarquables(): remarquableLines {
135
+ return this._remarquables;
136
+ }
137
+
138
+ // ------------------------------------------
139
+ // Creation / parsing functions
140
+ // ------------------------------------------
141
+
142
+ /**
143
+ * Parse values to a triangle. Supported formats:
144
+ * Point, Point, Point
145
+ * x1, y1, x2, y2, x3, y3
146
+ * TODO: Something else ?
147
+ * @param values
148
+ */
149
+ parse = (...values: any): Triangle => {
150
+ if (values.length === 6) {
151
+ // Check if all values are number or fractions.
152
+ let v = values.map((x: any) => new Fraction(x));
153
+ return this.parse(
154
+ new Point(v[0], v[1]),
155
+ new Point(v[2], v[3]),
156
+ new Point(v[4], v[5]),
157
+ )
158
+ } else if (values.length === 3) {
159
+ // Possibilities:
160
+ // - Three points (or part of points, only dict for example, or array (TODO: Add the array syntax for point)
161
+ // - Three lines
162
+ // - Three lines as text.
163
+ if(values.filter((x:any) => typeof x === 'string').length===3) {
164
+ return this.parse( ...values.map((x:string) => new Line(x)) )
165
+ }else if(values.filter((x:any) => x instanceof Line).length===3) {
166
+ // We have three lines
167
+ this._lines = {
168
+ 'AB': values[0],
169
+ 'BC': values[1],
170
+ 'AC': values[2]
171
+ };
172
+
173
+ // Get the intersection points -> build the triangle using these intersection points.
174
+ let intersect = values[0].intersection(values[1]);
175
+ if (intersect.hasIntersection) {
176
+ this._B = intersect.point.clone();
177
+ } else {
178
+ return this;
179
+ }
180
+ intersect = values[1].intersection(values[2]);
181
+ if (intersect.hasIntersection) {
182
+ this._C = intersect.point.clone();
183
+ } else {
184
+ return this;
185
+ }
186
+ intersect = values[2].intersection(values[0]);
187
+ if (intersect.hasIntersection) {
188
+ this._A = intersect.point.clone();
189
+ } else {
190
+ return this;
191
+ }
192
+ }else {
193
+ // At least, one of the value is not a point.
194
+ if (values.filter((x: any) => (x instanceof Point)).length < 3) {
195
+ return this.parse(
196
+ new Point(values[0]),
197
+ new Point(values[1]),
198
+ new Point(values[2])
199
+ )
200
+ }
201
+
202
+ // We have three points.
203
+ this._A = values[0].clone();
204
+ this._B = values[1].clone();
205
+ this._C = values[2].clone();
206
+
207
+ this._lines = {
208
+ 'AB': new Line(this._A, this._B),
209
+ 'BC': new Line(this._B, this._C),
210
+ 'AC': new Line(this._A, this._C)
211
+ };
212
+ }
213
+ } else if (values.length === 1) {
214
+ if (values[0] instanceof Triangle) {
215
+ return values[0].clone();
216
+ }
217
+ }
218
+
219
+ this._updateTriangle();
220
+ return this;
221
+ }
222
+
223
+ /**
224
+ * Clone the Triangle class
225
+ */
226
+ clone = (): Triangle => {
227
+ this._A = this._A.clone();
228
+ this._B = this._B.clone();
229
+ this._C = this._C.clone();
230
+
231
+ this._lines = {
232
+ 'AB': this._lines.AB.clone(),
233
+ 'BC': this._lines.BC.clone(),
234
+ 'AC': this._lines.AC.clone()
235
+ }
236
+
237
+ this._updateTriangle();
238
+ return this;
239
+ }
240
+
241
+
242
+ // ------------------------------------------
243
+ // Triangle operations and properties
244
+ // ------------------------------------------
245
+
246
+ /**
247
+ * Generate the Line object for the three segments of the triangle
248
+ */
249
+ private _updateTriangle = () => {
250
+ this._middles = {
251
+ 'AB': new Point().middleOf(this._A, this._B),
252
+ 'AC': new Point().middleOf(this._A, this._C),
253
+ 'BC': new Point().middleOf(this._B, this._C)
254
+ }
255
+
256
+ this._remarquables = this._calculateRemarquableLines();
257
+ }
258
+
259
+
260
+ /**
261
+ * Get the Point class for the given name
262
+ * @param ptName
263
+ */
264
+ private getPointByName = (ptName: string): Point => {
265
+ switch (ptName.toUpperCase()) {
266
+ case 'A':
267
+ return this._A;
268
+ case 'B':
269
+ return this._B;
270
+ case 'C':
271
+ return this._C;
272
+ }
273
+
274
+ // Something went wrong ! Return the first point
275
+ return this._A;
276
+ }
277
+ /**
278
+ * Get the vector for the segment given by name.
279
+ * @param ptName1
280
+ * @param ptName2
281
+ */
282
+ private getSegment = (ptName1: string, ptName2: string): Vector => {
283
+ return new Vector(
284
+ this.getPointByName(ptName1),
285
+ this.getPointByName(ptName2)
286
+ );
287
+ }
288
+
289
+ private _calculateRemarquableLines = (): remarquableLines => {
290
+ const bA= this._calculateBisectors('A'),
291
+ bB= this._calculateBisectors('B'),
292
+ bC= this._calculateBisectors('C')
293
+
294
+ let remarquables: remarquableLines = {
295
+ 'medians': {
296
+ 'A': new Line(this._A, this._middles.BC),
297
+ 'B': new Line(this._B, this._middles.AC),
298
+ 'C': new Line(this._C, this._middles.AB),
299
+ 'intersection': null
300
+ },
301
+ 'mediators': {
302
+ 'AB': new Line(this._middles.AB, new Vector(this._A, this._B).normal()),
303
+ 'AC': new Line(this._middles.AC, new Vector(this._A, this._C).normal()),
304
+ 'BC': new Line(this._middles.BC, new Vector(this._B, this._C).normal()),
305
+ 'intersection': null
306
+ },
307
+ 'heights': {
308
+ 'A': new Line(this._A, new Vector(this._B, this._C).normal()),
309
+ 'B': new Line(this._B, new Vector(this._A, this._C).normal()),
310
+ 'C': new Line(this._C, new Vector(this._A, this._B).normal()),
311
+ 'intersection': null
312
+ },
313
+ 'bisectors': {
314
+ 'A': bA.internal,
315
+ 'B': bB.internal,
316
+ 'C': bB.internal,
317
+ 'intersection': null
318
+ },
319
+ externalBisectors: {
320
+ 'A': bA.external,
321
+ 'B': bB.external,
322
+ 'C': bC.external,
323
+ 'intersection': null
324
+ }
325
+ }
326
+
327
+ // As it's a triangle, we assume the lines are intersecting and aren't parallel or superposed.
328
+ remarquables.medians.intersection = remarquables.medians.A.intersection(remarquables.medians.B).point;
329
+ remarquables.mediators.intersection = remarquables.mediators.AB.intersection(remarquables.mediators.BC).point;
330
+ remarquables.heights.intersection = remarquables.heights.A.intersection(remarquables.heights.B).point;
331
+ remarquables.bisectors.intersection = remarquables.bisectors.A.intersection(remarquables.bisectors.B).point;
332
+
333
+ // Everything was calculated for the remarquable lines.
334
+ return remarquables;
335
+ }
336
+
337
+ private _calculateBisectors = (pt: string): { internal: Line, external: Line } => {
338
+ let tlines = this.lines, d1, d2;
339
+
340
+ if(pt==='A'){
341
+ d1 = tlines.AB;
342
+ d2 = tlines.AC;
343
+ }else if(pt==='B'){
344
+ d1 = tlines.AB;
345
+ d2 = tlines.BC;
346
+ }else if(pt==='C'){
347
+ d1 = tlines.BC;
348
+ d2 = tlines.AC;
349
+ }
350
+
351
+ let b1 = new Line(new Equation(d1.equation.left.clone().multiply(d2.n.simplify().norm), d2.equation.left.clone().multiply(d1.n.simplify().norm)).reorder(true).simplify()),
352
+ b2 = new Line(new Equation(d1.equation.left.clone().multiply(d2.n.simplify().norm), d2.equation.left.clone().multiply(d1.n.simplify().norm).opposed()).reorder(true).simplify());
353
+
354
+ // Must determine which bisectors is in the triangle
355
+ if(pt==='A'){
356
+ return b1.hitSegment(this.B, this.C)?{internal:b1, external: b2}:{internal:b2, external: b1};
357
+ }
358
+ if(pt==='B'){
359
+ return b1.hitSegment(this.A, this.C)?{internal:b1, external: b2}:{internal:b2, external: b1};
360
+ }
361
+ if(pt==='C'){
362
+ return b1.hitSegment(this.B, this.A)?{internal:b1, external: b2}:{internal:b2, external: b1};
363
+ }
364
+
365
+ // Default returns the first bisector
366
+ return {internal:b1, external: b2}
367
+ }
368
+ }