LogarithmPlotter/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/utils.mjs
Ad5001 08fea34366
All checks were successful
continuous-integration/drone/push Build is passing
Converting as many JS libraries to ECMAScript modules.
2024-03-28 03:09:37 +01:00

402 lines
14 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const powerpos = {
"-": "⁻",
"+": "⁺",
"=": "⁼",
" ": "",
"(": "⁽",
")": "⁾",
"0": "⁰",
"1": "¹",
"2": "²",
"3": "³",
"4": "⁴",
"5": "⁵",
"6": "⁶",
"7": "⁷",
"8": "⁸",
"9": "⁹",
"a": "ᵃ",
"b": "ᵇ",
"c": "ᶜ",
"d": "ᵈ",
"e": "ᵉ",
"f": "ᶠ",
"g": "ᵍ",
"h": "ʰ",
"i": "ⁱ",
"j": "ʲ",
"k": "ᵏ",
"l": "ˡ",
"m": "ᵐ",
"n": "ⁿ",
"o": "ᵒ",
"p": "ᵖ",
"r": "ʳ",
"s": "ˢ",
"t": "ᵗ",
"u": "ᵘ",
"v": "ᵛ",
"w": "ʷ",
"x": "ˣ",
"y": "ʸ",
"z": "ᶻ"
}
const exponents = [
"⁰","¹","²","³","⁴","⁵","⁶","⁷","⁸","⁹"
]
const exponentReg = new RegExp('(['+exponents.join('')+']+)', 'g')
const indicepos = {
"-": "₋",
"+": "₊",
"=": "₌",
"(": "₍",
")": "₎",
" ": "",
"0": "₀",
"1": "₁",
"2": "₂",
"3": "₃",
"4": "₄",
"5": "₅",
"6": "₆",
"7": "₇",
"8": "₈",
"9": "₉",
"a": "ₐ",
"e": "ₑ",
"h": "ₕ",
"i": "ᵢ",
"j": "ⱼ",
"k": "ₖ",
"l": "ₗ",
"m": "ₘ",
"n": "ₙ",
"o": "ₒ",
"p": "ₚ",
"r": "ᵣ",
"s": "ₛ",
"t": "ₜ",
"u": "ᵤ",
"v": "ᵥ",
"x": "ₓ",
}
// Put a text in sup position
export function textsup(text) {
let ret = ""
text = text.toString()
for (let i = 0; i < text.length; i++) {
if(Object.keys(powerpos).indexOf(text[i]) >= 0) {
ret += powerpos[text[i]]
} else {
ret += text[i]
}
}
return ret
}
// Put a text in sub position
export function textsub(text) {
let ret = ""
text = text.toString()
for (let i = 0; i < text.length; i++) {
if(Object.keys(indicepos).indexOf(text[i]) >= 0) {
ret += indicepos[text[i]]
} else {
ret += text[i]
}
}
return ret
}
/**
* Simplifies (mathematically) a mathematical expression.
* @param {string} str - Expression to parse
* @returns {string}
*/
export function simplifyExpression(str) {
let replacements = [
// Operations not done by parser.
// [// Decomposition way 2
// /(^|[+-] |\()([-.\d\w]+) ([*/]) \((([-.\d\w] [*/] )?[-\d\w.]+) ([+\-]) (([-.\d\w] [*/] )?[\d\w.+]+)\)($| [+-]|\))/g,
// "$1$2 $3 $4 $6 $2 $3 $7$9"
// ],
// [ // Decomposition way 2
// /(^|[+-] |\()\((([-.\d\w] [*/] )?[-\d\w.]+) ([+\-]) (([-.\d\w] [*/] )?[\d\w.+]+)\) ([*/]) ([-.\d\w]+)($| [+-]|\))/g,
// "$1$2 $7 $8 $4 $5 $7 $8$9"
// ],
[ // Factorisation of π elements.
/(([-\d\w.]+ [*/] )*)(pi|π)(( [/*] [-\d\w.]+)*) ([+-]) (([-\d\w.]+ [*/] )*)(pi|π)(( [/*] [-\d\w.]+)*)?/g,
function(match, m1, n1, pi1, m2, ope2, n2, opeM, m3, n3, pi2, m4, ope4, n4) {
// g1, g2, g3 , g4, g5, g6, g7, g8, g9, g10, g11,g12 , g13
// We don't care about mx & pix, ope2 & ope4 are either / or * for n2 & n4.
// n1 & n3 are multiplied, opeM is the main operation (- or +).
// Putting all n in form of number
//n2 = n2 == undefined ? 1 : parseFloat(n)
n1 = m1 === undefined ? 1 : eval(m1 + '1')
n2 = m2 === undefined ? 1 : eval('1' + m2)
n3 = m3 === undefined ? 1 : eval(m3 + '1')
n4 = m4 === undefined ? 1 : eval('1' + m4)
//let [n1, n2, n3, n4] = [n1, n2, n3, n4].map(n => n == undefined ? 1 : parseFloat(n))
// Falling back to * in case it does not exist (the corresponding n would be 1)
[ope2, ope4] = [ope2, ope4].map(ope => ope === '/' ? '/' : '*')
let coeff1 = n1*n2
let coeff2 = n3*n4
let coefficient = coeff1+coeff2-(opeM === '-' ? 2*coeff2 : 0)
return `${coefficient} * π`
}
],
[ // Removing parenthesis when content is only added from both sides.
/(^|[+-] |\()\(([^)(]+)\)($| [+-]|\))/g,
function(match, b4, middle, after) {return `${b4}${middle}${after}`}
],
[ // Removing parenthesis when content is only multiplied.
/(^|[*\/] |\()\(([^)(+-]+)\)($| [*\/+-]|\))/g,
function(match, b4, middle, after) {return `${b4}${middle}${after}`}
],
[ // Removing parenthesis when content is only multiplied.
/(^|[*\/+-] |\()\(([^)(+-]+)\)($| [*\/]|\))/g,
function(match, b4, middle, after) {return `${b4}${middle}${after}`}
],
[// Simplification additions/subtractions.
/(^|[^*\/] |\()([-.\d]+) [+-] (\([^)(]+\)|[^)(]+) [+-] ([-.\d]+)($| [^*\/]|\))/g,
function(match, b4, n1, op1, middle, op2, n2, after) {
let total
if(op2 === '+') {
total = parseFloat(n1) + parseFloat(n2)
} else {
total = parseFloat(n1) - parseFloat(n2)
}
return `${b4}${total} ${op1} ${middle}${after}`
}
],
[// Simplification multiplications/divisions.
/([-.\d]+) [*\/] (\([^)(]+\)|[^)(+-]+) [*\/] ([-.\d]+)/g,
function(match, n1, op1, middle, op2, n2) {
if(parseInt(n1) === n1 && parseInt(n2) === n2 && op2 === '/' &&
(parseInt(n1) / parseInt(n2)) % 1 !== 0) {
// Non int result for int division.
return `(${n1} / ${n2}) ${op1} ${middle}`
} else {
if(op2 === '*') {
return `${parseFloat(n1) * parseFloat(n2)} ${op1} ${middle}`
} else {
return `${parseFloat(n1) / parseFloat(n2)} ${op1} ${middle}`
}
}
}
],
[// Starting & ending parenthesis if not needed.
/^\s*\((.*)\)\s*$/g,
function(match, middle) {
let str = middle
// Replace all groups
while(/\([^)(]+\)/g.test(str))
str = str.replace(/\([^)(]+\)/g, '')
// There shouldn't be any more parenthesis
// If there is, that means the 2 parenthesis are needed.
if(!str.includes(')') && !str.includes('(')) {
return middle
} else {
return `(${middle})`
}
}
],
// Simple simplifications
// [/(\s|^|\()0(\.0+)? \* (\([^)(]+\))/g, '$10'],
// [/(\s|^|\()0(\.0+)? \* ([^)(+-]+)/g, '$10'],
// [/(\([^)(]\)) \* 0(\.0+)?(\s|$|\))/g, '0$3'],
// [/([^)(+-]) \* 0(\.0+)?(\s|$|\))/g, '0$3'],
// [/(\s|^|\()1(\.0+)? [\*\/] /g, '$1'],
// [/(\s|^|\()0(\.0+)? (\+|\-) /g, '$1'],
// [/ [\*\/] 1(\.0+)?(\s|$|\))/g, '$3'],
// [/ (\+|\-) 0(\.0+)?(\s|$|\))/g, '$3'],
// [/(^| |\() /g, '$1'],
// [/ ($|\))/g, '$1'],
]
// Replacements
let found
do {
found = false
for(let replacement of replacements)
while(replacement[0].test(str)) {
found = true
str = str.replace(replacement[0], replacement[1])
}
} while(found)
return str
}
/**
* Transforms a mathematical expression to make it readable by humans.
* NOTE: Will break parsing of expression.
* @param {string} str - Expression to parse.
* @returns {string}
*/
export function makeExpressionReadable(str) {
let replacements = [
// letiables
[/pi/g, 'π'],
[/Infinity/g, '∞'],
[/inf/g, '∞'],
// Other
[/ \* /g, '×'],
[/ \^ /g, '^'],
[/\^\(([\d\w+-]+)\)/g, function(match, p1) { return textsup(p1) }],
[/\^([\d\w+-]+)/g, function(match, p1) { return textsup(p1) }],
[/_\(([\d\w+-]+)\)/g, function(match, p1) { return textsub(p1) }],
[/_([\d\w+-]+)/g, function(match, p1) { return textsub(p1) }],
[/\[([^\[\]]+)\]/g, function(match, p1) { return textsub(p1) }],
[/(\d|\))×/g, '$1'],
//[/×(\d|\()/g, '$1'],
[/([^a-z])\(([^)(+.\/-]+)\)/g, "$1×$2"],
[/integral\((.+),\s?(.+),\s?["'](.+)["'],\s?["'](.+)["']\)/g, function(match, a, b, p1, body, p2, p3, by, p4) {
if(a.length < b.length) {
return `${textsub(a)}${textsup(b)} ${body} d${by}`
} else {
return `${textsup(b)}${textsub(a)} ${body} d${by}`
}
}],
[/derivative\(?["'](.+)["'], ?["'](.+)["'], ?(.+)\)?/g, function(match, p1, body, p2, p3, by, p4, x) {
return `d(${body.replace(new RegExp(by, 'g'), 'x')})/dx`
}]
]
// str = simplifyExpression(str)
// Replacements
for(let replacement of replacements)
while(replacement[0].test(str))
str = str.replace(replacement[0], replacement[1])
return str
}
/**
* Parses a variable name to make it readable by humans.
*
* @param {string} str - Variable name to parse
* @param {boolean} removeUnallowed - Remove domain symbols disallowed in name.
* @returns {string} - The parsed name
*/
export function parseName(str, removeUnallowed = true) {
let replacements = [
// Greek letters
[/([^a-z]|^)al(pha)?([^a-z]|$)/g, '$1α$3'],
[/([^a-z]|^)be(ta)?([^a-z]|$)/g, '$1β$3'],
[/([^a-z]|^)ga(mma)?([^a-z]|$)/g, '$1γ$3'],
[/([^a-z]|^)de(lta)?([^a-z]|$)/g, '$1δ$3'],
[/([^a-z]|^)ep(silon)?([^a-z]|$)/g, '$1ε$3'],
[/([^a-z]|^)ze(ta)?([^a-z]|$)/g, '$1ζ$3'],
[/([^a-z]|^)et(a)?([^a-z]|$)/g, '$1η$3'],
[/([^a-z]|^)th(eta)?([^a-z]|$)/g, '$1θ$3'],
[/([^a-z]|^)io(ta)?([^a-z]|$)/g, '$1ι$3'],
[/([^a-z]|^)ka(ppa)([^a-z]|$)?/g, '$1κ$3'],
[/([^a-z]|^)la(mbda)?([^a-z]|$)/g, '$1λ$3'],
[/([^a-z]|^)mu([^a-z]|$)/g, '$1μ$2'],
[/([^a-z]|^)nu([^a-z]|$)/g, '$1ν$2'],
[/([^a-z]|^)xi([^a-z]|$)/g, '$1ξ$2'],
[/([^a-z]|^)rh(o)?([^a-z]|$)/g, '$1ρ$3'],
[/([^a-z]|^)si(gma)?([^a-z]|$)/g, '$1σ$3'],
[/([^a-z]|^)ta(u)?([^a-z]|$)/g, '$1τ$3'],
[/([^a-z]|^)up(silon)?([^a-z]|$)/g, '$1υ$3'],
[/([^a-z]|^)ph(i)?([^a-z]|$)/g, '$1φ$3'],
[/([^a-z]|^)ch(i)?([^a-z]|$)/g, '$1χ$3'],
[/([^a-z]|^)ps(i)?([^a-z]|$)/g, '$1ψ$3'],
[/([^a-z]|^)om(ega)?([^a-z]|$)/g, '$1ω$3'],
// Capital greek letters
[/([^a-z]|^)gga(mma)?([^a-z]|$)/g, '$1Γ$3'],
[/([^a-z]|^)gde(lta)?([^a-z]|$)/g, '$1Δ$3'],
[/([^a-z]|^)gth(eta)?([^a-z]|$)/g, '$1Θ$3'],
[/([^a-z]|^)gla(mbda)?([^a-z]|$)/g, '$1Λ$3'],
[/([^a-z]|^)gxi([^a-z]|$)/g, '$1Ξ$2'],
[/([^a-z]|^)gpi([^a-z]|$)/g, '$1Π$2'],
[/([^a-z]|^)gsi(gma)([^a-z]|$)?/g, '$1Σ$3'],
[/([^a-z]|^)gph(i)?([^a-z]|$)/g, '$1Φ$3'],
[/([^a-z]|^)gps(i)?([^a-z]|$)/g, '$1Ψ$3'],
[/([^a-z]|^)gom(ega)?([^a-z]|$)/g, '$1Ω$3'],
// Underscores
// [/_\(([^_]+)\)/g, function(match, p1) { return textsub(p1) }],
// [/_([^" ]+)/g, function(match, p1) { return textsub(p1) }],
// Array elements
[/\[([^\]\[]+)\]/g, function(match, p1) { return textsub(p1) }],
// Removing
[/[xπ\\∪∩\]\[ ()^/÷*×+=\d-]/g , ''],
]
if(!removeUnallowed) replacements.pop()
// Replacements
for(let replacement of replacements)
str = str.replace(replacement[0], replacement[1])
return str
}
String.prototype.toLatinUppercase = function() {
return this.replace(/[a-z]/g, function(match){return match.toUpperCase()})
}
/**
* Transforms camel case strings to a space separated one.
*
* @param {string} label - Camel case to parse
* @returns {string} Parsed label.
*/
export function camelCase2readable(label) {
let parsed = parseName(label, false)
return parsed.charAt(0).toLatinUppercase() + parsed.slice(1).replace(/([A-Z])/g," $1")
}
/**
* Creates a randomized color string.
* @returns {string}
*/
export function getRandomColor() {
let clrs = '0123456789ABCDEF';
let color = '#';
for(let i = 0; i < 6; i++) {
color += clrs[Math.floor(Math.random() * (16-5*(i%2===0)))];
}
return color;
}
/**
* Escapes text to html entities.
* @param {string} str
* @returns {string}
*/
export function escapeHTML(str) {
return str.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;') ;
}
/**
* Parses exponents and replaces them with expression values
* @param {string} expression - The expression to replace in.
* @return {string} The parsed expression
*/
export function exponentsToExpression(expression) {
return expression.replace(exponentReg, (m, exp) => '^' + exp.split('').map((x) => exponents.indexOf(x)).join(''))
}