Compare commits
No commits in common. "master" and "rollup-js" have entirely different histories.
6
.gitignore
vendored
|
@ -37,10 +37,8 @@ docs/html
|
|||
*.lpf
|
||||
*.lgg
|
||||
|
||||
# Tests
|
||||
common/coverage/
|
||||
**/.coverage
|
||||
|
||||
# npm
|
||||
common/node_modules
|
||||
common/coverage/
|
||||
common/.coverage
|
||||
runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/index.mjs*
|
||||
|
|
18
README.md
|
@ -1,4 +1,4 @@
|
|||
# ![icon](https://apps.ad5001.eu/icons/apps/svg/logarithmplotter.svg) LogarithmPlotter
|
||||
# ![icon](https://git.ad5001.eu/Ad5001/LogarithmPlotter/raw/branch/master/logplotter.svg) LogarithmPlotter
|
||||
|
||||
[![Build Status](https://ci.ad5001.eu/api/badges/Ad5001/LogarithmPlotter/status.svg)](https://ci.ad5001.eu/Ad5001/LogarithmPlotter)
|
||||
[![Translation status](https://hosted.weblate.org/widgets/logarithmplotter/-/logarithmplotter/svg-badge.svg)](https://hosted.weblate.org/engage/logarithmplotter/)
|
||||
|
@ -24,7 +24,7 @@ First, you'll need to install all the required dependencies:
|
|||
- [npm](https://npmjs.com) (or [yarn](https://yarnpkg.com/)), go to the `common` directory, and run `npm install` (or `yarn install`).
|
||||
|
||||
You can simply run LogarithmPlotter using `python3 run.py`. It automatically compiles the language files (requires
|
||||
`pyside6-lrelease` to be installed and in path), and the JavaScript modules.
|
||||
`lrelease` to be installed and in path), and the JavaScript modules.
|
||||
|
||||
If you do not wish do recompile the files again on every run, you can use the build script (`scripts/build.sh`) and run
|
||||
`python3 build/runtime-pyside6/LogarithmPlotter/logarithmplotter.py`.
|
||||
|
@ -68,13 +68,7 @@ To run LogarithmPlotter's tests, follow these steps:
|
|||
|
||||
- Python
|
||||
- Install python3 and [poetry](https://python-poetry.org/)
|
||||
- Create and activate virtual env (recommended)
|
||||
- Go into `runtime-pyside6` and run `poetry install --with test`
|
||||
- ECMAScript
|
||||
- Install node with npm
|
||||
- Go into `common` and run `npm install -D`
|
||||
|
||||
Finally, to actually run the tests:
|
||||
- Run `poetry install --with test`
|
||||
- Run `scripts/run-tests.sh`
|
||||
|
||||
## Legal notice
|
||||
|
@ -95,8 +89,8 @@ Finally, to actually run the tests:
|
|||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
See LICENSE.md for more details. Language files translations located at assets/i18n are licensed under GNU GPL3.0+ and
|
||||
are copyrighted by their original authors:
|
||||
Language files translations located at LogarithmPlotter/i18n are licensed under GNU GPL3.0+ and are copyrighted by their
|
||||
original authors. See LICENSE.md for more details:
|
||||
|
||||
- 🇭🇺 Hungarian translation by [Óvári](https://github.com/ovari)
|
||||
- 🇳🇴 Norwegian translation by [Allan Nordhøy](https://github.com/comradekingu)
|
||||
|
@ -109,5 +103,5 @@ of [ndef.parser](https://web.archive.org/web/20111023001618/http://www.undefined
|
|||
<r@undefined.ch>, ported to javascript by Matthew Crumley
|
||||
<email@matthewcrumley.com> (http://silentmatt.com/), and then to QMLJS by Ad5001.
|
||||
|
||||
All files in (common/src/lib/expr-eval/) except integration.mjs are licensed
|
||||
All files in (LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/) except integration.mjs are licensed
|
||||
under the [MIT License](https://raw.githubusercontent.com/silentmatt/expr-eval/master/LICENSE.txt).
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
#!/bin/bash
|
||||
pyside6-lrelease *.ts
|
||||
lrelease *.ts
|
||||
|
|
|
@ -21,7 +21,7 @@ replace() {
|
|||
|
||||
rm ../qml/eu/ad5001/LogarithmPlotter/js/index.mjs # Remove index which should not be scanned
|
||||
|
||||
files=$(find ../../common/src -name '*.mjs')
|
||||
files=$(find .. -name *.mjs)
|
||||
for file in $files; do
|
||||
echo "Moving '$file' to '${file%.*}.js'..."
|
||||
mv "$file" "${file%.*}.js"
|
||||
|
@ -33,14 +33,12 @@ for file in $files; do
|
|||
replace "${file%.*}.js" "^export" "/*export*/"
|
||||
replace "${file%.*}.js" "async " "/*async */"
|
||||
replace "${file%.*}.js" "await" "/*await */"
|
||||
replace "${file%.*}.js" " #" "// #"
|
||||
replace "${file%.*}.js" "this.#" "/*this.#*/"
|
||||
done
|
||||
|
||||
echo "----------------------------"
|
||||
echo "| Updating translations... |"
|
||||
echo "----------------------------"
|
||||
pyside6-lupdate -extensions js,qs,qml,py -recursive ../../common/src -recursive ../../runtime-pyside6/LogarithmPlotter -ts lp_*.ts
|
||||
lupdate -extensions js,qs,qml,py -recursive .. -ts lp_*.ts
|
||||
# Updating locations in files
|
||||
for lp in *.ts; do
|
||||
echo "Replacing locations in $lp..."
|
||||
|
@ -57,9 +55,7 @@ for file in $files; do
|
|||
replace "$file" "/*async */" "async "
|
||||
replace "$file" "^/*export*/" "export"
|
||||
replace "$file" "^/*export default*/" "export default"
|
||||
replace "$file" '.mjs"*/' '.mjs"'
|
||||
replace "$file" "^/*import" "import"
|
||||
replace "$file" "^/*export" "export"
|
||||
replace "$file" "// #" " #"
|
||||
replace "$file" "/*this.#*/" "this.#"
|
||||
replace "$file" '.mjs"*/$' '.mjs"'
|
||||
done
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
../common/appearance.svg
|
|
@ -1 +0,0 @@
|
|||
../common/appearance.svg
|
|
@ -1 +0,0 @@
|
|||
../common/arrow.svg
|
|
@ -1 +0,0 @@
|
|||
../common/position.svg
|
|
@ -1 +0,0 @@
|
|||
../common/angle.svg
|
|
@ -1 +0,0 @@
|
|||
../common/angle.svg
|
|
@ -1 +0,0 @@
|
|||
../common/appearance.svg
|
|
@ -1 +0,0 @@
|
|||
../common/target.svg
|
|
@ -1 +0,0 @@
|
|||
../common/position.svg
|
|
@ -1 +0,0 @@
|
|||
../common/label.svg
|
|
@ -1 +0,0 @@
|
|||
../common/angle.svg
|
|
@ -1 +0,0 @@
|
|||
../common/position.svg
|
|
@ -1 +0,0 @@
|
|||
../common/position.svg
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
1
assets/icons/settings/custom/Display Mode.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../common/appearance.svg
|
1
assets/icons/settings/custom/Display Style.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../common/appearance.svg
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
1
assets/icons/settings/custom/Label Position.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../common/arrow.svg
|
1
assets/icons/settings/custom/Label X.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../common/position.svg
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
1
assets/icons/settings/custom/Phase.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../common/angle.svg
|
1
assets/icons/settings/custom/Point Style.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../common/appearance.svg
|
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 2 KiB |
1
assets/icons/settings/custom/Target Element.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../common/target.svg
|
1
assets/icons/settings/custom/Target Value Position.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../common/position.svg
|
1
assets/icons/settings/custom/Text.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../common/label.svg
|
1
assets/icons/settings/custom/Unit.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../common/angle.svg
|
1
assets/icons/settings/custom/X.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../common/position.svg
|
1
assets/icons/settings/custom/Y.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../common/position.svg
|
1
assets/icons/settings/custom/ω_0.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../common/angle.svg
|
|
@ -1,171 +1,64 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="48.0px"
|
||||
height="48.0px"
|
||||
viewBox="0 0 48.0 48.0"
|
||||
width="24.0px"
|
||||
height="24.0px"
|
||||
viewBox="0 0 24.0 24.0"
|
||||
version="1.1"
|
||||
id="SVGRoot"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xml:space="preserve"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<title
|
||||
id="title38896">LogarithmPlotter Icon</title>
|
||||
<defs
|
||||
id="defs2254">
|
||||
<linearGradient
|
||||
id="linearGradient27593">
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:0.15000001;"
|
||||
offset="0"
|
||||
id="stop27589" />
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop27591" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient13467">
|
||||
<stop
|
||||
style="stop-color:#808080;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop13463" />
|
||||
<stop
|
||||
style="stop-color:#666666;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop13465" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient8377">
|
||||
<stop
|
||||
style="stop-color:#ebebeb;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop8373" />
|
||||
<stop
|
||||
style="stop-color:#bfbfbf;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop8375" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
xlink:href="#linearGradient8377"
|
||||
id="linearGradient8379"
|
||||
x1="12"
|
||||
y1="4.8570137"
|
||||
x2="12"
|
||||
y2="21.105883"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
xlink:href="#linearGradient13467"
|
||||
id="linearGradient13469"
|
||||
x1="12"
|
||||
y1="9.5647058"
|
||||
x2="12"
|
||||
y2="21"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
xlink:href="#linearGradient27593"
|
||||
id="linearGradient27595"
|
||||
x1="28"
|
||||
y1="28"
|
||||
x2="42"
|
||||
y2="42"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
</defs>
|
||||
<g
|
||||
id="layer1">
|
||||
<rect
|
||||
style="fill:url(#linearGradient13469);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><title
|
||||
id="title836">LogarithmPlotter Icon v1.0</title><defs
|
||||
id="defs833" /><metadata
|
||||
id="metadata836"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title>LogarithmPlotter Icon v1.0</dc:title><cc:license
|
||||
rdf:resource="http://creativecommons.org/licenses/by-nc-sa/4.0/" /><dc:date>2021</dc:date><dc:creator><cc:Agent><dc:title>Ad5001</dc:title></cc:Agent></dc:creator><dc:rights><cc:Agent><dc:title>(c) Ad5001 2021 - All rights reserved</dc:title></cc:Agent></dc:rights></cc:Work><cc:License
|
||||
rdf:about="http://creativecommons.org/licenses/by-nc-sa/4.0/"><cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Reproduction" /><cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Distribution" /><cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Notice" /><cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Attribution" /><cc:prohibits
|
||||
rdf:resource="http://creativecommons.org/ns#CommercialUse" /><cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /><cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#ShareAlike" /></cc:License></rdf:RDF></metadata><g
|
||||
id="layer2"
|
||||
transform="matrix(1,0,0,0.94444444,0,1.1666667)"
|
||||
style="fill:#666666"><rect
|
||||
style="fill:#666666;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1546"
|
||||
width="18"
|
||||
height="18.105883"
|
||||
x="3"
|
||||
y="2.8941176"
|
||||
ry="2.3823531"
|
||||
rx="2.2499998"
|
||||
transform="matrix(2.2222222,0,0,2.0987654,-2.6666667,-0.07407404)" />
|
||||
<rect
|
||||
style="fill:url(#linearGradient8379);display:inline;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1546-7"
|
||||
width="18"
|
||||
height="18.105883"
|
||||
height="18"
|
||||
x="3"
|
||||
y="3"
|
||||
ry="2.3212669"
|
||||
rx="2.2499998"
|
||||
transform="matrix(2.2222222,0,0,2.1539961,-2.6666667,-2.4619883)" />
|
||||
</g>
|
||||
<g
|
||||
id="layer3"
|
||||
style="fill:#0000ff">
|
||||
<path
|
||||
id="path27475"
|
||||
style="fill:url(#linearGradient27595);fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 20,8 V 36 H 10 l 7,7 h 15 7 c 2.769997,0 5,-2.230003 5,-5 v -5 -1 z" />
|
||||
</g>
|
||||
<g
|
||||
id="layer1-6"
|
||||
style="stroke-width:2;stroke-dasharray:none"
|
||||
transform="matrix(2,0,0,2,0,1)">
|
||||
<rect
|
||||
ry="2.25" /></g><g
|
||||
id="layer2-6"
|
||||
transform="matrix(1,0,0,0.94444444,0,0.16666668)"
|
||||
style="fill:#f9f9f9"><rect
|
||||
style="fill:#f9f9f9;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1546-7"
|
||||
width="18"
|
||||
height="18"
|
||||
x="3"
|
||||
y="3"
|
||||
ry="2.25" /></g><g
|
||||
id="layer1"
|
||||
style="stroke-width:2;stroke-dasharray:none"><rect
|
||||
style="fill:#000000;fill-rule:evenodd;stroke-width:1.86898;stroke-dasharray:none;stroke-opacity:0"
|
||||
id="rect1410"
|
||||
width="14"
|
||||
height="2"
|
||||
x="5"
|
||||
y="15.5" />
|
||||
<rect
|
||||
style="fill:#000000;fill-rule:evenodd;stroke-width:2.06559;stroke-dasharray:none;stroke-opacity:0"
|
||||
y="15.5" /><rect
|
||||
style="fill:#000000;fill-rule:evenodd;stroke-width:2;stroke-dasharray:none;stroke-opacity:0"
|
||||
id="rect1412"
|
||||
width="2"
|
||||
height="16"
|
||||
x="8"
|
||||
y="3.5" />
|
||||
<path
|
||||
height="15"
|
||||
x="9"
|
||||
y="3.9768662" /><path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ff0000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path1529"
|
||||
d="m 18,3.5 c 0,7 -4,12 -13,12" />
|
||||
</g>
|
||||
<metadata
|
||||
id="metadata38894">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:title>LogarithmPlotter Icon</dc:title>
|
||||
<dc:date>2024-10-06</dc:date>
|
||||
<dc:creator>
|
||||
<cc:Agent>
|
||||
<dc:title>Adsooi <mail@ad5001.eu></dc:title>
|
||||
</cc:Agent>
|
||||
</dc:creator>
|
||||
<dc:rights>
|
||||
<cc:Agent>
|
||||
<dc:title>(c) Adsooi 2021-2024</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:rights>
|
||||
<cc:license
|
||||
rdf:resource="http://creativecommons.org/licenses/by-nc-sa/4.0/" />
|
||||
</cc:Work>
|
||||
<cc:License
|
||||
rdf:about="http://creativecommons.org/licenses/by-nc-sa/4.0/">
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Reproduction" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Distribution" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Notice" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Attribution" />
|
||||
<cc:prohibits
|
||||
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
|
||||
</cc:License>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
</svg>
|
||||
d="M 18,4 C 18,10.017307 13.40948,15.5 5,15.5" /></g></svg>
|
||||
|
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 3.1 KiB |
|
@ -3,7 +3,7 @@ Source: logarithmplotter
|
|||
Version: 0.6.0
|
||||
Architecture: all
|
||||
Maintainer: Ad5001 <mail@ad5001.eu>
|
||||
Depends: python3 (>= 3.9), python3-pip, python3-pyside6-essentials (>= 6.7.0), python3-pyside6-addons (>= 6.7), texlive-latex-base, dvipng
|
||||
Depends: python3 (>= 3.9), python3-pip, python3-pyside6-essentials (>= 6.7.0), texlive-latex-base, dvipng
|
||||
|
||||
Build-Depends: debhelper (>=11~), dh-python, dpkg-dev (>= 1.16.1~), python-setuptools
|
||||
Section: science
|
|
@ -1 +0,0 @@
|
|||
python3 (>= 3.9), python3-pip, python3-pyside6.qtcore (>= 6), python3-pyside6.qtgui (>= 6), python3-pyside6.qtqml (>= 6), python3-pyside6.qtwidgets (>= 6), python3-pyside6.qtquick (>= 6), python3-pyside6.qtquickcontrols2 (>= 6), qml6-module-qt-labs-platform (>= 6), qml6-module-qtquick-dialogs (>= 6), texlive-latex-base, dvipng
|
|
@ -66,54 +66,50 @@
|
|||
<categories>
|
||||
<category>Science</category>
|
||||
<category>Education</category>
|
||||
<category>Qt</category>
|
||||
</categories>
|
||||
|
||||
<url type="homepage">https://apps.ad5001.eu/logarithmplotter/</url>
|
||||
<url type="bugtracker">https://git.ad5001.eu/Ad5001/LogarithmPlotter/issues/</url>
|
||||
<url type="help">https://git.ad5001.eu/Ad5001/LogarithmPlotter/wiki/</url>
|
||||
<url type="translate">https://hosted.weblate.org/engage/logarithmplotter/</url>
|
||||
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>https://apps.ad5001.eu/img/en/logarithmplotter/gain.png?v=0.6</image>
|
||||
<image xml:lang="de">https://apps.ad5001.eu/img/de/logarithmplotter/gain.png?v=0.6</image>
|
||||
<image xml:lang="fr">https://apps.ad5001.eu/img/fr/logarithmplotter/gain.png?v=0.6</image>
|
||||
<image xml:lang="hu">https://apps.ad5001.eu/img/hu/logarithmplotter/gain.png?v=0.6</image>
|
||||
<image xml:lang="no">https://apps.ad5001.eu/img/no/logarithmplotter/gain.png?v=0.6</image>
|
||||
<image xml:lang="es">https://apps.ad5001.eu/img/es/logarithmplotter/gain.png?v=0.6</image>
|
||||
<image>https://apps.ad5001.eu/img/en/logarithmplotter/gain.png?v=0.5</image>
|
||||
<image xml:lang="de">https://apps.ad5001.eu/img/de/logarithmplotter/gain.png?v=0.5</image>
|
||||
<image xml:lang="fr">https://apps.ad5001.eu/img/fr/logarithmplotter/gain.png?v=0.5</image>
|
||||
<image xml:lang="hu">https://apps.ad5001.eu/img/hu/logarithmplotter/gain.png?v=0.5</image>
|
||||
<image xml:lang="no">https://apps.ad5001.eu/img/no/logarithmplotter/gain.png?v=0.5</image>
|
||||
<caption>Main view of LogarithmPlotter showing an asymptotic Bode magnitude plot.</caption>
|
||||
<caption xml:lang="de">Die Hauptansicht des LogarithmPlotters zeigt eine asymptotische Bode-Magnitude-Darstellung.</caption>
|
||||
<caption xml:lang="fr">Vue principale de LogarithmPlotter montrant un tracé asymptotique d'une magnitude de Bode.</caption>
|
||||
<caption xml:lang="hu">A LogarithmPlotter fő nézete, amely egy aszimptotikus Bode-magnitúdó ábrát mutat.</caption>
|
||||
<caption xml:lang="no">Hovedvisning av LogarithmPlotter som viser et asymptotisk Bode-størrelsesplott.</caption>
|
||||
<caption xml:lang="es">Vista principal de LogarithmPlotter mostrando un gráfico asintótico de una magnitud de Bode.</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://apps.ad5001.eu/img/en/logarithmplotter/phase.png?v=0.6</image>
|
||||
<image xml:lang="de">https://apps.ad5001.eu/img/de/logarithmplotter/phase.png?v=0.6</image>
|
||||
<image xml:lang="fr">https://apps.ad5001.eu/img/fr/logarithmplotter/phase.png?v=0.6</image>
|
||||
<image xml:lang="hu">https://apps.ad5001.eu/img/hu/logarithmplotter/phase.png?v=0.6</image>
|
||||
<image xml:lang="no">https://apps.ad5001.eu/img/no/logarithmplotter/phase.png?v=0.6</image>
|
||||
<image xml:lang="es">https://apps.ad5001.eu/img/es/logarithmplotter/phase.png?v=0.6</image>
|
||||
<image>https://apps.ad5001.eu/img/en/logarithmplotter/phase.png?v=0.5</image>
|
||||
<image xml:lang="de">https://apps.ad5001.eu/img/de/logarithmplotter/phase.png?v=0.5</image>
|
||||
<image xml:lang="fr">https://apps.ad5001.eu/img/fr/logarithmplotter/phase.png?v=0.5</image>
|
||||
<image xml:lang="hu">https://apps.ad5001.eu/img/hu/logarithmplotter/phase.png?v=0.5</image>
|
||||
<image xml:lang="no">https://apps.ad5001.eu/img/no/logarithmplotter/phase.png?v=0.5</image>
|
||||
<caption>Main view of LogarithmPlotter showing an asymptotic Bode phase plot.</caption>
|
||||
<caption xml:lang="de">Hauptansicht des LogarithmPlotters mit einer asymptotischen Bode-Phasendarstellung.</caption>
|
||||
<caption xml:lang="fr">Vue principale de LogarithmPlotter montrant un tracé asymptotique d'une phase de Bode.</caption>
|
||||
<caption xml:lang="hu">A LogarithmPlotter fő nézete, amely egy aszimptotikus Bode-fázis ábrát mutat.</caption>
|
||||
<caption xml:lang="no">Hovedvisning av LogarithmPlotter som viser et asymptotisk Bode-fasediagram.</caption>
|
||||
<caption xml:lang="es">Vista principal de LogarithmPlotter mostrando un gráfico asintótico de una fase de Bode.</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://apps.ad5001.eu/img/en/logarithmplotter/welcome.png?v=0.6</image>
|
||||
<image xml:lang="de">https://apps.ad5001.eu/img/de/logarithmplotter/welcome.png?v=0.6</image>
|
||||
<image xml:lang="fr">https://apps.ad5001.eu/img/fr/logarithmplotter/welcome.png?v=0.6</image>
|
||||
<image xml:lang="hu">https://apps.ad5001.eu/img/hu/logarithmplotter/welcome.png?v=0.6</image>
|
||||
<image xml:lang="no">https://apps.ad5001.eu/img/no/logarithmplotter/welcome.png?v=0.6</image>
|
||||
<image xml:lang="es">https://apps.ad5001.eu/img/es/logarithmplotter/welcome.png?v=0.6</image>
|
||||
<image>https://apps.ad5001.eu/img/en/logarithmplotter/welcome.png?v=0.5</image>
|
||||
<image xml:lang="de">https://apps.ad5001.eu/img/de/logarithmplotter/welcome.png?v=0.5</image>
|
||||
<image xml:lang="fr">https://apps.ad5001.eu/img/fr/logarithmplotter/welcome.png?v=0.5</image>
|
||||
<image xml:lang="hu">https://apps.ad5001.eu/img/hu/logarithmplotter/welcome.png?v=0.5</image>
|
||||
<image xml:lang="no">https://apps.ad5001.eu/img/no/logarithmplotter/welcome.png?v=0.5</image>
|
||||
<caption>LogarithmPlotter's welcome page.</caption>
|
||||
<caption xml:lang="de">LogarithmPlotter's Willkommensseite.</caption>
|
||||
<caption xml:lang="fr">Page d'accueil de LogarithmPlotter.</caption>
|
||||
<caption xml:lang="hu">LogarithmPlotter üdvözlő oldala.</caption>
|
||||
<caption xml:lang="no">LogarithmPlotters velkomstside.</caption>
|
||||
<caption xml:lang="es">Página de bienvenida de LogarithmPlotter.</caption>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
|
||||
<mime-type xmlns="http://www.freedesktop.org/standards/shared-mime-info" type="application/x-logarithm-plot">
|
||||
<comment>Logarithmic Plot File</comment>
|
||||
<comment>Logarithm Plot File</comment>
|
||||
<comment xml:lang="fr">Fichier Graphe Logarithmique</comment>
|
||||
<icon name="application-x-logarithm-plot"/>
|
||||
<glob-deleteall/>
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
LPFv1{"xzoom":100,"yzoom":10,"xmin":0.2696454905834007,"ymax":33.115625,"xaxisstep":"4","yaxisstep":"π","xaxislabel":"","yaxislabel":"","logscalex":true,"linewidth":1,"showxgrad":true,"showygrad":true,"textsize":18,"history":[[["CreateNewObject",["A","Point",["A",true,"#941A97","name + value","1","0","above","●"]]],["EditedPosition",["A","Point","1","175.36","0","9.9"]],["CreateNewObject",["f","Function",["f",true,"#6E590E","name + value","x","ℝ⁺*","ℝ","application","above",1,true,true]]],["EditedProperty",["f","Function","expression","x","((x / 2) - 1)",true]],["CreateNewObject",["t","Text",["t",true,"#118455","null","1","0","center","New text",false]]],["EditedPosition",["t","Text","1","36.48","0","(-13.7)"]],["EditedProperty",["t","Text","text","New text","AEZA",false]],["CreateNewObject",["ω","Point",["ω",true,"#5A3A52","name","1","0","above","●"]]],["CreateNewObject",["G₀","Gain Bode",["G₀",true,"#5A3A52","name + value","ω","high","20","below",1,false]]],["EditedPosition",["ω","Point","1","17.76","0","(-8.9)"]],["EditedProperty",["G₀","Gain Bode","gain","20","10",true]],["EditedProperty",["G₀","Gain Bode","labelPosition","below","below-left",false]],["EditedProperty",["G₀","Gain Bode","pass","high","low",false]],["EditedProperty",["G₀","Gain Bode","labelX",1,62.61,false]],["CreateNewObject",["X","X Cursor",["X",true,"#5909A9","name + value","1",null,"left",true,3,"— — — — — — —","Next to target"]]],["EditedProperty",["X","X Cursor","x","1","5.04",true]],["CreateNewObject",["u","Sequence",["u",true,"#78929E","name + value",true,true,{"1":"n"},{"0":0},"above",1]]],["EditedProperty",["u","Sequence","defaultExpression",{"1":"n"},{"1":"n+1"},false]],["EditedProperty",["u","Sequence","defaultExpression",{"1":"n+1"},{"1":"n+1"},false]],["EditedProperty",["u","Sequence","baseValues",{"0":0},{"0":"-1"},false]],["EditedProperty",["u","Sequence","baseValues",{"0":"-1"},{"0":"-1"},false]],["CreateNewObject",["F_X","Repartition",["F_X",true,"#231931","name + value",{"0":"0"},"above",1]]],["EditedProperty",["F_X","Repartition","labelX",1,12.64,false]],["EditedProperty",["f","Function","labelPosition","above","right",false]],["EditedProperty",["f","Function","labelX",1,30,false]],["EditedProperty",["u","Sequence","labelX",1,3,false]],["EditedProperty",["F_X","Repartition","labelX",12.64,40,false]],["EditedProperty",["ω","Point","labelPosition","above","below",false]],["CreateNewObject",["ω₀","Point",["ω₀","#7C2981","name","name + value","1","0","above","●"]]],["CreateNewObject",["φ₀","Phase Bode",["φ₀",true,"#7C2981","name + value","ω₀","90","°","below",1]]],["EditedPosition",["ω₀","Point","1","3","0","(-8)"]],["EditedPosition",["ω₀","Point","3","2","(-8)","8"]],["EditedProperty",["ω₀","Point","labelPosition","above","above-right",false]],["EditedProperty",["u","Sequence","labelPosition","above","above-left",false]],["EditedProperty",["u","Sequence","labelX",3,20,false]],["EditedProperty",["G","Somme gains Bode","labelX",1,2,false]]],[]],"width":1000,"height":500,"objects":{"Point":[["A",true,"#941A97","name + value","175.36","9.9","above","●"],["ω",true,"#5A3A52","name","17.76","(-8.9)","below","●"],["ω₀",false,"name","name","2","8","above-right","●"]],"Function":[["f",true,"#6E590E","name + value","((x / 2) - 1)","ℝ⁺*","ℝ","application","right",30,true,true]],"Text":[["t",true,"#118455","null","36.48","(-13.7)","center","AEZA",false]],"Gain Bode":[["G₀",true,"#5A3A52","name + value","ω","low","10","below-left",62.61,false]],"Somme gains Bode":[["G",true,"#A83C72","name + value","above",2]],"X Cursor":[["X",true,"#5909A9","name + value","5.04",null,"left",true,null,"— — — — — — —","Next to target"]],"Sequence":[["u",true,"#78929E","name + value",true,true,{"1":"n+1"},{"0":"-1"},"above-left",20]],"Repartition":[["F_X",true,"#231931","name + value",{"0":"0"},"above",40]],"Phase Bode":[["φ₀",true,"#7C2981","name + value","ω₀","90","°","below",1]],"Somme phases Bode":[["φ",true,"#A08B14","name + value","above",1]]},"type":"logplotv1"}
|
17
ci/drone.yml
|
@ -12,29 +12,32 @@ steps:
|
|||
- git submodule update --init --recursive
|
||||
|
||||
- name: Build
|
||||
image: ad5001/ubuntu-pyside-xvfb:linux-6-latest-latex-node
|
||||
image: node:18-bookworm
|
||||
commands:
|
||||
- cd common && npm install && cd ..
|
||||
- apt update
|
||||
- apt install -y qtchooser qttools5-dev-tools
|
||||
# Start building
|
||||
- bash scripts/build.sh
|
||||
when:
|
||||
event: [ push, tag ]
|
||||
|
||||
- name: Unit Tests
|
||||
image: ad5001/ubuntu-pyside-xvfb:linux-6-latest-latex-node
|
||||
image: ad5001/ubuntu-pyside-xvfb:linux-6-latest-latex
|
||||
commands:
|
||||
- apt update
|
||||
- apt install -y npm
|
||||
- cd common && npm install -D && cd ..
|
||||
- xvfb-run bash scripts/run-tests.sh --no-rebuild
|
||||
when:
|
||||
event: [ push, tag ]
|
||||
|
||||
- name: File Tests
|
||||
image: ad5001/ubuntu-pyside-xvfb:linux-6-latest-latex-node
|
||||
image: ad5001/ubuntu-pyside-xvfb:linux-6-latest-latex
|
||||
commands:
|
||||
- xvfb-run python3 run.py --test-build --no-check-for-updates
|
||||
- xvfb-run python3 run.py --test-build --no-check-for-updates ./ci/test1.lpf
|
||||
- xvfb-run python3 run.py --test-build --no-check-for-updates ./ci/test2.lpf
|
||||
- xvfb-run python3 run.py --test-build --no-check-for-updates ./ci/all.lpf
|
||||
- xvfb-run python3 run.py --test-build --no-check-for-updates ./ci/magnitude.lpf
|
||||
- xvfb-run python3 run.py --test-build --no-check-for-updates ./ci/phase.lpf
|
||||
- xvfb-run python3 run.py --test-build --no-check-for-updates ./ci/stress.lpf
|
||||
when:
|
||||
event: [ push, tag ]
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
LPFv1{"xzoom":100,"yzoom":100,"xmin":0.5,"ymax":2,"xaxisstep":"4","yaxisstep":"pi/4","xaxislabel":"ω (rad/s)","yaxislabel":"φ (rad)","logscalex":true,"linewidth":2,"showxgrad":true,"showygrad":true,"textsize":20,"history":[[["CreateNewObject",["f","Function",["f",true,"#989E2D","name + value","x","ℝ⁺*","ℝ","application","above",1,true,true]]],["EditedProperty",["f","Function","expression","x","(x ^ 20)",true]],["EditedProperty",["f","Function","expression","(x ^ 20)","(20 * (log10 x))",true]],["DeleteObject",["f","Function",["f",true,"#989E2D","name + value","(20 * (log10 x))","ℝ⁺*","ℝ","application","above",1,true,true]]],["CreateNewObject",["ω","Point",["ω",true,"#995178","name","1","0","bottom","●"]]],["CreateNewObject",["φ₀","Phase Bode",["φ₀",true,"#995178","name + value","ω","90","°","below",1]]],["EditedProperty",["φ₀","Phase Bode","phase","90","0",true]],["EditedProperty",["φ₀","Phase Bode","unit","°","rad",false]],["EditedProperty",["ω","Point","y","0","((-pi) / 2)",true]],["EditedVisibility",["ω","Point","visible"]],["EditedProperty",["φ₀","Phase Bode","labelX",1,10,false]],["CreateNewObject",["ω₀","Point",["ω₀",true,"#037753","name","1","0","bottom","●"]]],["CreateNewObject",["φ₁","Phase Bode",["φ₁",true,"#037753","name + value","ω₀","90","°","below",1]]],["EditedProperty",["ω₀","Point","x","1","10",true]],["EditedProperty",["φ₁","Phase Bode","unit","°","rad",false]],["EditedProperty",["φ₁","Phase Bode","phase","90","(pi / 2)",true]],["EditedProperty",["ω₀","Point","x","10","5",true]],["EditedProperty",["ω₀","Point","labelPosition","bottom","top-left",false]],["EditedProperty",["φ₁","Phase Bode","labelX",1,2,false]],["EditedProperty",["φ","Somme phases Bode","labelX",1,2,false]],["ColorChanged",["φ","Somme phases Bode","#665B74","#550000"]],["EditedProperty",["ω₀","Point","labelPosition","top-left","above-left",false]],["EditedProperty",["ω₀","Point","labelPosition","above-left","above-right",false]]],[]],"width":1000,"height":500,"objects":{"Function":[],"Point":[["ω",false,"#995178","name","1","((-pi) / 2)","below","●"],["ω₀",true,"#037753","name","5","0","above-right","●"]],"Phase Bode":[["φ₀",true,"#995178","name","ω","0","rad","below",10],["φ₁",true,"#037753","name","ω₀","(pi / 2)","rad","below",2]],"Somme phases Bode":[["φ",true,"#550000","name + value","above",2]]},"type":"logplotv1"}
|
1539
common/package-lock.json
generated
|
@ -2,7 +2,7 @@
|
|||
"name": "logarithmplotter",
|
||||
"version": "0.6.0",
|
||||
"description": "2D plotter software to make Bode plots, sequences and distribution functions.",
|
||||
"main": "src/index.mjs",
|
||||
"main": "LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/autoload.mjs",
|
||||
"scripts": {
|
||||
"build": "rollup --config rollup.config.mjs",
|
||||
"test": "c8 mocha test/**/*.mjs"
|
||||
|
@ -24,12 +24,9 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^5.0.0",
|
||||
"@types/chai-spies": "^1.0.6",
|
||||
"@types/chai-as-promised": "^8.0.1",
|
||||
"@types/mocha": "^10.0.8",
|
||||
"chai": "^5.1.1",
|
||||
"chai-as-promised": "^8.0.0",
|
||||
"chai-spies": "^1.1.0",
|
||||
"esm": "^3.2.25",
|
||||
"mocha": "^10.7.3"
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ import { babel } from "@rollup/plugin-babel"
|
|||
import cleanup from "rollup-plugin-cleanup"
|
||||
|
||||
const src = "./src/index.mjs"
|
||||
const dest = "../build/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Common/index.mjs"
|
||||
const dest = "../build/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/index.mjs"
|
||||
|
||||
export default {
|
||||
input: src,
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
/**
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* We do not inherit the DOM's Event, because not only the DOM part is unnecessary,
|
||||
* but also because it does not exist within Qt environments.
|
||||
*/
|
||||
|
||||
|
||||
export class BaseEvent {
|
||||
___name = ""
|
||||
|
||||
/**
|
||||
* @property {string} name - Name of the event.
|
||||
*/
|
||||
constructor(name) {
|
||||
this.___name = name
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.___name
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Base class for all classes which can emit events.
|
||||
*/
|
||||
export class BaseEventEmitter {
|
||||
static emits = []
|
||||
|
||||
/** @type {Record<string, Set<function>>} */
|
||||
#listeners = {}
|
||||
|
||||
constructor() {
|
||||
for(const eventType of this.constructor.emits) {
|
||||
this.#listeners[eventType] = new Set()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to an event that can be emitted by this object.
|
||||
*
|
||||
* @param {string} eventType - Name of the event to listen to. Throws an error if this object does not emit this kind of event.
|
||||
* @param {function(BaseEvent)} eventListener - The function to be called back when the event is emitted.
|
||||
*/
|
||||
on(eventType, eventListener) {
|
||||
if(eventType.includes(" ")) // Listen to several different events with the same listener.
|
||||
for(const type of eventType.split(" "))
|
||||
this.on(type, eventListener)
|
||||
else {
|
||||
if(!this.constructor.emits.includes(eventType)) {
|
||||
const className = this.constructor.name
|
||||
const eventTypes = this.constructor.emits.join(", ")
|
||||
throw new Error(`Cannot listen to unknown event ${eventType} in class ${className}. ${className} only emits: ${eventTypes}`)
|
||||
}
|
||||
if(!this.#listeners[eventType].has(eventListener))
|
||||
this.#listeners[eventType].add(eventListener)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a listener from an event that can be emitted by this object.
|
||||
*
|
||||
* @param {string} eventType - Name of the event that was listened to. Throws an error if this object does not emit this kind of event.
|
||||
* @param {function(BaseEvent)} eventListener - The function previously registered as a listener.
|
||||
* @returns {boolean} True if the listener was removed, false if it was not found.
|
||||
*/
|
||||
off(eventType, eventListener) {
|
||||
if(eventType.includes(" ")) { // Unlisten to several different events with the same listener.
|
||||
let found = false
|
||||
for(const type of eventType.split(" "))
|
||||
found ||= this.off(type, eventListener)
|
||||
return found
|
||||
} else {
|
||||
if(!this.constructor.emits.includes(eventType)) {
|
||||
const className = this.constructor.name
|
||||
const eventTypes = this.constructor.emits.join(", ")
|
||||
throw new Error(`Cannot listen to unknown event ${eventType} in class ${className}. ${className} only emits: ${eventTypes}`)
|
||||
}
|
||||
return this.#listeners[eventType].delete(eventListener)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event to all of its listeners.
|
||||
*
|
||||
* @param {BaseEvent} e
|
||||
*/
|
||||
emit(e) {
|
||||
if(!(e instanceof BaseEvent))
|
||||
throw new Error("Cannot emit non event object.")
|
||||
if(!this.constructor.emits.includes(e.name)) {
|
||||
const className = this.constructor.name
|
||||
const eventTypes = this.constructor.emits.join(", ")
|
||||
throw new Error(`Cannot emit event '${e.name}' from class ${className}. ${className} can only emit: ${eventTypes}`)
|
||||
}
|
||||
for(const listener of this.#listeners[e.name])
|
||||
listener(e)
|
||||
}
|
||||
}
|
|
@ -95,15 +95,11 @@ export class Action {
|
|||
if(!Latex.enabled)
|
||||
throw new Error("Cannot render an item as LaTeX when LaTeX is disabled.")
|
||||
const imgDepth = History.imageDepth
|
||||
const renderArguments = [
|
||||
const { source, width, height } = await Latex.requestAsyncRender(
|
||||
latexString,
|
||||
imgDepth * (History.fontSize + 2),
|
||||
History.themeTextColor
|
||||
]
|
||||
let render = Latex.findPrerendered(...renderArguments)
|
||||
if(render === null)
|
||||
render = await Latex.requestAsyncRender(...renderArguments)
|
||||
const { source, width, height } = render
|
||||
)
|
||||
return `<img src="${source}" width="${width / imgDepth}" height="${height / imgDepth}" style="vertical-align: middle"/>`
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
import Objects from "../module/objects.mjs"
|
||||
import Latex from "../module/latex.mjs"
|
||||
import * as MathLib from "../math/index.mjs"
|
||||
import { escapeHTML } from "../utils/index.mjs"
|
||||
import { escapeHTML } from "../utils.mjs"
|
||||
import { Action } from "./common.mjs"
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,11 +18,10 @@
|
|||
|
||||
import js from "./lib/polyfills/js.mjs"
|
||||
|
||||
export * as Utils from "./utils/index.mjs"
|
||||
|
||||
import * as Modules from "./module/index.mjs"
|
||||
import * as ObjsAutoload from "./objs/autoload.mjs"
|
||||
|
||||
export * as Modules from "./module/index.mjs"
|
||||
export * as MathLib from "./math/index.mjs"
|
||||
export * as HistoryLib from "./history/index.mjs"
|
||||
export * as Parsing from "./parsing/index.mjs"
|
||||
export * as Utils from "./utils.mjs"
|
||||
|
|
|
@ -111,7 +111,7 @@ function simplify(tokens, unaryOps, binaryOps, ternaryOps, values) {
|
|||
* In the given instructions, replaces variable by expr.
|
||||
* @param {Instruction[]} tokens
|
||||
* @param {string} variable
|
||||
* @param {ExprEvalExpression} expr
|
||||
* @param {number} expr
|
||||
* @return {Instruction[]}
|
||||
*/
|
||||
function substitute(tokens, variable, expr) {
|
||||
|
@ -171,6 +171,9 @@ function evaluate(tokens, expr, values) {
|
|||
nstack.push(n1 ? !!evaluate(n2, expr, values) : false)
|
||||
} else if(item.value === "or") {
|
||||
nstack.push(n1 ? true : !!evaluate(n2, expr, values))
|
||||
} else if(item.value === "=") {
|
||||
f = expr.binaryOps[item.value]
|
||||
nstack.push(f(n1, evaluate(n2, expr, values), values))
|
||||
} else {
|
||||
f = expr.binaryOps[item.value]
|
||||
nstack.push(f(resolveExpression(n1, values), resolveExpression(n2, values)))
|
||||
|
@ -487,6 +490,18 @@ export class ExprEvalExpression {
|
|||
return evaluate(this.tokens, this, values)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of symbols (string of characters) in the expressions.
|
||||
* Can be functions, constants, or variables.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
symbols(options) {
|
||||
options = options || {}
|
||||
const vars = []
|
||||
getSymbols(this.tokens, vars, options)
|
||||
return vars
|
||||
}
|
||||
|
||||
toString() {
|
||||
return expressionToString(this.tokens, false)
|
||||
}
|
||||
|
|
|
@ -47,7 +47,9 @@ const optionNameMap = {
|
|||
"not": "logical",
|
||||
"?": "conditional",
|
||||
":": "conditional",
|
||||
//'=': 'assignment', // Disable assignment
|
||||
"[": "array"
|
||||
//'()=': 'fndef' // Diable function definition
|
||||
}
|
||||
|
||||
export class Parser {
|
||||
|
@ -107,6 +109,7 @@ export class Parser {
|
|||
and: Polyfill.andOperator,
|
||||
or: Polyfill.orOperator,
|
||||
"in": Polyfill.inOperator,
|
||||
"=": Polyfill.setVar,
|
||||
"[": Polyfill.arrayIndex
|
||||
}
|
||||
|
||||
|
@ -120,13 +123,18 @@ export class Parser {
|
|||
min: Polyfill.min,
|
||||
max: Polyfill.max,
|
||||
hypot: Math.hypot || Polyfill.hypot,
|
||||
pyt: Math.hypot || Polyfill.hypot,
|
||||
pyt: Math.hypot || Polyfill.hypot, // backward compat
|
||||
pow: Math.pow,
|
||||
atan2: Math.atan2,
|
||||
"if": Polyfill.condition,
|
||||
gamma: Polyfill.gamma,
|
||||
"Γ": Polyfill.gamma,
|
||||
roundTo: Polyfill.roundTo,
|
||||
map: Polyfill.arrayMap,
|
||||
fold: Polyfill.arrayFold,
|
||||
filter: Polyfill.arrayFilter,
|
||||
indexOf: Polyfill.stringOrArrayIndexOf,
|
||||
join: Polyfill.arrayJoin
|
||||
}
|
||||
|
||||
// These constants will automatically be replaced the MOMENT they are parsed.
|
||||
|
@ -151,6 +159,10 @@ export class Parser {
|
|||
return new ExprEvalExpression(instr, this)
|
||||
}
|
||||
|
||||
evaluate(expr, variables) {
|
||||
return this.parse(expr).evaluate(variables)
|
||||
}
|
||||
|
||||
isOperatorEnabled(op) {
|
||||
const optionName = optionNameMap.hasOwnProperty(op) ? optionNameMap[op] : op
|
||||
const operators = this.options.operators || {}
|
||||
|
|
|
@ -210,8 +210,9 @@ export function gamma(n) {
|
|||
}
|
||||
|
||||
export function stringOrArrayLength(s) {
|
||||
if(Array.isArray(s))
|
||||
if(Array.isArray(s)) {
|
||||
return s.length
|
||||
}
|
||||
return String(s).length
|
||||
}
|
||||
|
||||
|
@ -266,6 +267,11 @@ export function roundTo(value, exp) {
|
|||
return +(value[0] + "e" + (value[1] ? (+value[1] + exp) : exp))
|
||||
}
|
||||
|
||||
export function setVar(name, value, variables) {
|
||||
if(variables) variables[name] = value
|
||||
return value
|
||||
}
|
||||
|
||||
export function arrayIndex(array, index) {
|
||||
return array[index | 0]
|
||||
}
|
||||
|
@ -290,6 +296,58 @@ export function min(array) {
|
|||
}
|
||||
}
|
||||
|
||||
export function arrayMap(f, a) {
|
||||
if(typeof f !== "function") {
|
||||
throw new EvalError(qsTranslate("error", "First argument to map is not a function."))
|
||||
}
|
||||
if(!Array.isArray(a)) {
|
||||
throw new EvalError(qsTranslate("error", "Second argument to map is not an array."))
|
||||
}
|
||||
return a.map(function(x, i) {
|
||||
return f(x, i)
|
||||
})
|
||||
}
|
||||
|
||||
export function arrayFold(f, init, a) {
|
||||
if(typeof f !== "function") {
|
||||
throw new EvalError(qsTranslate("error", "First argument to fold is not a function."))
|
||||
}
|
||||
if(!Array.isArray(a)) {
|
||||
throw new EvalError(qsTranslate("error", "Second argument to fold is not an array."))
|
||||
}
|
||||
return a.reduce(function(acc, x, i) {
|
||||
return f(acc, x, i)
|
||||
}, init)
|
||||
}
|
||||
|
||||
export function arrayFilter(f, a) {
|
||||
if(typeof f !== "function") {
|
||||
throw new EvalError(qsTranslate("error", "First argument to filter is not a function."))
|
||||
}
|
||||
if(!Array.isArray(a)) {
|
||||
throw new EvalError(qsTranslate("error", "Second argument to filter is not an array."))
|
||||
}
|
||||
return a.filter(function(x, i) {
|
||||
return f(x, i)
|
||||
})
|
||||
}
|
||||
|
||||
export function stringOrArrayIndexOf(target, s) {
|
||||
if(!(Array.isArray(s) || typeof s === "string")) {
|
||||
throw new Error(qsTranslate("error", "Second argument to indexOf is not a string or array."))
|
||||
}
|
||||
|
||||
return s.indexOf(target)
|
||||
}
|
||||
|
||||
export function arrayJoin(sep, a) {
|
||||
if(!Array.isArray(a)) {
|
||||
throw new Error(qsTranslate("error", "Second argument to join is not an array."))
|
||||
}
|
||||
|
||||
return a.join(sep)
|
||||
}
|
||||
|
||||
export function sign(x) {
|
||||
return ((x > 0) - (x < 0)) || +x
|
||||
}
|
||||
|
|
|
@ -472,7 +472,7 @@ export class TokenStream {
|
|||
this.current = this.newToken(TOP, "==")
|
||||
this.pos++
|
||||
} else {
|
||||
return false
|
||||
this.current = this.newToken(TOP, c)
|
||||
}
|
||||
} else if(c === "!") {
|
||||
if(this.expression.charAt(this.pos + 1) === "=") {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/*!
|
||||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
|
@ -64,20 +64,10 @@ function arrayFlatMap(callbackFn, thisArg) {
|
|||
* @return {String}
|
||||
*/
|
||||
function stringReplaceAll(from, to) {
|
||||
return this.split(from).join(to)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of an element of the array at a given index.
|
||||
* Accepts negative indexes.
|
||||
* @this {Array|string}
|
||||
* @param {number} index
|
||||
* @return {*}
|
||||
*/
|
||||
function arrayAt(index) {
|
||||
if(typeof index !== "number")
|
||||
throw new Error(`${index} is not a number`)
|
||||
return index >= 0 ? this[index] : this[this.length + index]
|
||||
let str = this
|
||||
while(str.includes(from))
|
||||
str = str.replace(from, to)
|
||||
return str
|
||||
}
|
||||
|
||||
|
||||
|
@ -108,8 +98,8 @@ const polyfills = {
|
|||
[String.prototype, "replaceAll", stringReplaceAll]
|
||||
],
|
||||
2022: [
|
||||
[Array.prototype, "at", arrayAt],
|
||||
[String.prototype, "at", arrayAt],
|
||||
[Array.prototype, "at", notPolyfilled("Array.prototype.at")],
|
||||
[String.prototype, "at", notPolyfilled("String.prototype.at")],
|
||||
[Object, "hasOwn", notPolyfilled("Object.hasOwn")]
|
||||
],
|
||||
2023: [
|
||||
|
@ -133,4 +123,4 @@ for(const [year, entries] of Object.entries(polyfills)) {
|
|||
for(const [context, functionName, polyfill] of entries.filter(x => x[0][x[1]] === undefined)) {
|
||||
context[functionName] = polyfill
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,38 +17,28 @@
|
|||
*/
|
||||
|
||||
|
||||
import * as Utils from "../utils/index.mjs"
|
||||
import { ExprEvalExpression } from "../lib/expr-eval/expression.mjs"
|
||||
import * as Utils from "../utils.mjs"
|
||||
import Latex from "../module/latex.mjs"
|
||||
import ExprParser from "../module/expreval.mjs"
|
||||
import Objects from "../module/objects.mjs"
|
||||
|
||||
const NUMBER_MATCHER = /^\d*\.\d+(e[+-]\d+)?$/
|
||||
|
||||
/**
|
||||
* Represents any kind of x-based or non variable based expression.
|
||||
*/
|
||||
export class Expression {
|
||||
/**
|
||||
*
|
||||
* @param {string|ExprEvalExpression} expr
|
||||
*/
|
||||
constructor(expr) {
|
||||
if(typeof expr === "string") {
|
||||
this.expr = Utils.exponentsToExpression(expr)
|
||||
this.calc = ExprParser.parse(this.expr).simplify()
|
||||
} else if(expr instanceof ExprEvalExpression) {
|
||||
} else {
|
||||
// Passed an expression here directly.
|
||||
this.calc = expr.simplify()
|
||||
this.expr = expr.toString()
|
||||
} else {
|
||||
const type = expr != null ? "a " + expr.constructor.name : expr
|
||||
throw new Error(`Cannot create an expression with ${type}.`)
|
||||
}
|
||||
this.canBeCached = this.isConstant()
|
||||
this.cached = this.isConstant()
|
||||
this.cachedValue = null
|
||||
if(this.canBeCached && this.allRequirementsFulfilled())
|
||||
this.recache()
|
||||
if(this.cached && this.allRequirementsFulfilled())
|
||||
this.cachedValue = this.calc.evaluate(Objects.currentObjectsByName)
|
||||
this.latexMarkup = Latex.expression(this.calc.tokens)
|
||||
}
|
||||
|
||||
|
@ -87,20 +77,21 @@ export class Expression {
|
|||
|
||||
/**
|
||||
* Returns a list of names whose corresponding objects this expression is dependant on and are missing.
|
||||
* @return {string[]}
|
||||
* @return {boolean}
|
||||
*/
|
||||
undefinedVariables() {
|
||||
return this.requiredObjects().filter(objName => !(objName in Objects.currentObjectsByName))
|
||||
}
|
||||
|
||||
recache() {
|
||||
this.cachedValue = this.calc.evaluate(Objects.currentObjectsByName)
|
||||
if(this.cached)
|
||||
this.cachedValue = this.calc.evaluate(Objects.currentObjectsByName)
|
||||
}
|
||||
|
||||
execute(x = 1) {
|
||||
if(this.canBeCached) {
|
||||
if(this.cached) {
|
||||
if(this.cachedValue == null)
|
||||
this.recache()
|
||||
this.cachedValue = this.calc.evaluate(Objects.currentObjectsByName)
|
||||
return this.cachedValue
|
||||
}
|
||||
ExprParser.currentVars = Object.assign({ "x": x }, Objects.currentObjectsByName)
|
||||
|
@ -108,10 +99,9 @@ export class Expression {
|
|||
}
|
||||
|
||||
simplify(x) {
|
||||
let expr = new Expression(this.calc.substitute("x", x).simplify())
|
||||
if(expr.allRequirementsFulfilled() && expr.execute() === 0)
|
||||
expr = new Expression("0")
|
||||
return expr
|
||||
let expr = this.calc.substitute("x", x).simplify()
|
||||
if(expr.evaluate() === 0) expr = "0"
|
||||
return new Expression(expr)
|
||||
}
|
||||
|
||||
toEditableString() {
|
||||
|
@ -120,28 +110,17 @@ export class Expression {
|
|||
|
||||
toString(forceSign = false) {
|
||||
let str = Utils.makeExpressionReadable(this.calc.toString())
|
||||
if(str !== undefined && str.match(NUMBER_MATCHER)) {
|
||||
const decimals = str.split(".")[1].split("e")[0]
|
||||
const zeros = decimals.split("0").length
|
||||
const nines = decimals.split("9").length
|
||||
if(zeros > 7 || nines > 7) {
|
||||
if(str !== undefined && str.match(/^\d*\.\d+$/)) {
|
||||
if(str.split(".")[1].split("0").length > 7) {
|
||||
// Likely rounding error
|
||||
str = parseFloat(str).toDecimalPrecision(8).toString()
|
||||
str = parseFloat(str.substring(0, str.length - 1)).toString()
|
||||
}
|
||||
}
|
||||
if(str[0] === "(" && str.at(-1) === ")")
|
||||
str = str.substring(1, str.length - 1)
|
||||
if(str[0] !== "-" && forceSign)
|
||||
str = "+" + str
|
||||
if(str[0] !== "-" && forceSign) str = "+" + str
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses and executes the given expression
|
||||
* @param {string} expr
|
||||
* @return {number}
|
||||
*/
|
||||
export function executeExpression(expr) {
|
||||
return (new Expression(expr.toString())).execute()
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
*/
|
||||
|
||||
import * as Expr from "./expression.mjs"
|
||||
import * as Utils from "../utils/index.mjs"
|
||||
import * as Utils from "../utils.mjs"
|
||||
import Latex from "../module/latex.mjs"
|
||||
import Objects from "../module/objects.mjs"
|
||||
import ExprParser from "../module/expreval.mjs"
|
||||
|
|
|
@ -18,33 +18,30 @@
|
|||
|
||||
import { Module } from "./common.mjs"
|
||||
import { CanvasInterface, DialogInterface } from "./interface.mjs"
|
||||
import { textsup } from "../utils/index.mjs"
|
||||
import { textsup } from "../utils.mjs"
|
||||
import { Expression } from "../math/index.mjs"
|
||||
import Latex from "./latex.mjs"
|
||||
import Objects from "./objects.mjs"
|
||||
import History from "./history.mjs"
|
||||
import Settings from "./settings.mjs"
|
||||
|
||||
|
||||
class CanvasAPI extends Module {
|
||||
|
||||
|
||||
/** @type {CanvasInterface} */
|
||||
#canvas = null
|
||||
/** @type {CanvasRenderingContext2D} */
|
||||
#ctx = null
|
||||
/** Lock to prevent asynchronous stuff from printing stuff that is outdated. */
|
||||
#redrawCount = 0
|
||||
/** @type {{show(string, string, string)}} */
|
||||
#drawingErrorDialog = null
|
||||
|
||||
|
||||
constructor() {
|
||||
super("Canvas", {
|
||||
canvas: CanvasInterface,
|
||||
drawingErrorDialog: DialogInterface
|
||||
})
|
||||
|
||||
/** @type {CanvasInterface} */
|
||||
this._canvas = null
|
||||
|
||||
/** @type {CanvasRenderingContext2D} */
|
||||
this._ctx = null
|
||||
|
||||
/**
|
||||
* @type {{show(string, string, string)}}
|
||||
* @private
|
||||
*/
|
||||
this._drawingErrorDialog = null
|
||||
/**
|
||||
*
|
||||
* @type {Object.<string, {expression: Expression, value: number, maxDraw: number}>}
|
||||
|
@ -70,18 +67,18 @@ class CanvasAPI extends Module {
|
|||
*/
|
||||
initialize({ canvas, drawingErrorDialog }) {
|
||||
super.initialize({ canvas, drawingErrorDialog })
|
||||
this.#canvas = canvas
|
||||
this.#drawingErrorDialog = drawingErrorDialog
|
||||
this._canvas = canvas
|
||||
this._drawingErrorDialog = drawingErrorDialog
|
||||
}
|
||||
|
||||
get width() {
|
||||
if(!this.initialized) throw new Error("Attempting width before initialize!")
|
||||
return this.#canvas.width
|
||||
return this._canvas.width
|
||||
}
|
||||
|
||||
get height() {
|
||||
if(!this.initialized) throw new Error("Attempting height before initialize!")
|
||||
return this.#canvas.height
|
||||
return this._canvas.height
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -90,7 +87,7 @@ class CanvasAPI extends Module {
|
|||
*/
|
||||
get xmin() {
|
||||
if(!this.initialized) throw new Error("Attempting xmin before initialize!")
|
||||
return Settings.xmin
|
||||
return this._canvas.xmin
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -99,7 +96,7 @@ class CanvasAPI extends Module {
|
|||
*/
|
||||
get xzoom() {
|
||||
if(!this.initialized) throw new Error("Attempting xzoom before initialize!")
|
||||
return Settings.xzoom
|
||||
return this._canvas.xzoom
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -108,7 +105,7 @@ class CanvasAPI extends Module {
|
|||
*/
|
||||
get ymax() {
|
||||
if(!this.initialized) throw new Error("Attempting ymax before initialize!")
|
||||
return Settings.ymax
|
||||
return this._canvas.ymax
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -117,7 +114,7 @@ class CanvasAPI extends Module {
|
|||
*/
|
||||
get yzoom() {
|
||||
if(!this.initialized) throw new Error("Attempting yzoom before initialize!")
|
||||
return Settings.yzoom
|
||||
return this._canvas.yzoom
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -126,7 +123,7 @@ class CanvasAPI extends Module {
|
|||
*/
|
||||
get xlabel() {
|
||||
if(!this.initialized) throw new Error("Attempting xlabel before initialize!")
|
||||
return Settings.xlabel
|
||||
return this._canvas.xlabel
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -135,7 +132,7 @@ class CanvasAPI extends Module {
|
|||
*/
|
||||
get ylabel() {
|
||||
if(!this.initialized) throw new Error("Attempting ylabel before initialize!")
|
||||
return Settings.ylabel
|
||||
return this._canvas.ylabel
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -144,7 +141,7 @@ class CanvasAPI extends Module {
|
|||
*/
|
||||
get linewidth() {
|
||||
if(!this.initialized) throw new Error("Attempting linewidth before initialize!")
|
||||
return Settings.linewidth
|
||||
return this._canvas.linewidth
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -153,7 +150,7 @@ class CanvasAPI extends Module {
|
|||
*/
|
||||
get textsize() {
|
||||
if(!this.initialized) throw new Error("Attempting textsize before initialize!")
|
||||
return Settings.textsize
|
||||
return this._canvas.textsize
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -162,7 +159,7 @@ class CanvasAPI extends Module {
|
|||
*/
|
||||
get logscalex() {
|
||||
if(!this.initialized) throw new Error("Attempting logscalex before initialize!")
|
||||
return Settings.logscalex
|
||||
return this._canvas.logscalex
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -171,7 +168,7 @@ class CanvasAPI extends Module {
|
|||
*/
|
||||
get showxgrad() {
|
||||
if(!this.initialized) throw new Error("Attempting showxgrad before initialize!")
|
||||
return Settings.showxgrad
|
||||
return this._canvas.showxgrad
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -180,7 +177,7 @@ class CanvasAPI extends Module {
|
|||
*/
|
||||
get showygrad() {
|
||||
if(!this.initialized) throw new Error("Attempting showygrad before initialize!")
|
||||
return Settings.showygrad
|
||||
return this._canvas.showygrad
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -204,7 +201,7 @@ class CanvasAPI extends Module {
|
|||
|
||||
requestPaint() {
|
||||
if(!this.initialized) throw new Error("Attempting requestPaint before initialize!")
|
||||
this.#canvas.requestPaint()
|
||||
this._canvas.requestPaint()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -212,18 +209,17 @@ class CanvasAPI extends Module {
|
|||
*/
|
||||
redraw() {
|
||||
if(!this.initialized) throw new Error("Attempting redraw before initialize!")
|
||||
this.#redrawCount = (this.#redrawCount + 1) % 10000
|
||||
this.#ctx = this.#canvas.getContext("2d")
|
||||
this._ctx = this._canvas.getContext("2d")
|
||||
this._computeAxes()
|
||||
this._reset()
|
||||
this._drawGrid()
|
||||
this._drawAxes()
|
||||
this._drawLabels()
|
||||
this.#ctx.lineWidth = this.linewidth
|
||||
this._ctx.lineWidth = this.linewidth
|
||||
for(let objType in Objects.currentObjects) {
|
||||
for(let obj of Objects.currentObjects[objType]) {
|
||||
this.#ctx.strokeStyle = obj.color
|
||||
this.#ctx.fillStyle = obj.color
|
||||
this._ctx.strokeStyle = obj.color
|
||||
this._ctx.fillStyle = obj.color
|
||||
if(obj.visible)
|
||||
try {
|
||||
obj.draw(this)
|
||||
|
@ -231,12 +227,12 @@ class CanvasAPI extends Module {
|
|||
// Drawing throws an error. Generally, it's due to a new modification (or the opening of a file)
|
||||
console.error(e)
|
||||
console.log(e.stack)
|
||||
this.#drawingErrorDialog.show(objType, obj.name, e.message)
|
||||
this._drawingErrorDialog.show(objType, obj.name, e.message)
|
||||
History.undo()
|
||||
}
|
||||
}
|
||||
}
|
||||
this.#ctx.lineWidth = 1
|
||||
this._ctx.lineWidth = 1
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -244,9 +240,9 @@ class CanvasAPI extends Module {
|
|||
* @private
|
||||
*/
|
||||
_computeAxes() {
|
||||
let exprY = new Expression(`x*(${Settings.yaxisstep})`)
|
||||
let exprY = new Expression(`x*(${this._canvas.yaxisstep})`)
|
||||
let y1 = exprY.execute(1)
|
||||
let exprX = new Expression(`x*(${Settings.xaxisstep})`)
|
||||
let exprX = new Expression(`x*(${this._canvas.xaxisstep})`)
|
||||
let x1 = exprX.execute(1)
|
||||
this.axesSteps = {
|
||||
x: {
|
||||
|
@ -268,10 +264,10 @@ class CanvasAPI extends Module {
|
|||
*/
|
||||
_reset() {
|
||||
// Reset
|
||||
this.#ctx.fillStyle = "#FFFFFF"
|
||||
this.#ctx.strokeStyle = "#000000"
|
||||
this.#ctx.font = `${this.textsize}px sans-serif`
|
||||
this.#ctx.fillRect(0, 0, this.width, this.height)
|
||||
this._ctx.fillStyle = "#FFFFFF"
|
||||
this._ctx.strokeStyle = "#000000"
|
||||
this._ctx.font = `${this.textsize}px sans-serif`
|
||||
this._ctx.fillRect(0, 0, this.width, this.height)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -279,7 +275,7 @@ class CanvasAPI extends Module {
|
|||
* @private
|
||||
*/
|
||||
_drawGrid() {
|
||||
this.#ctx.strokeStyle = "#C0C0C0"
|
||||
this._ctx.strokeStyle = "#C0C0C0"
|
||||
if(this.logscalex) {
|
||||
for(let xpow = -this.maxgradx; xpow <= this.maxgradx; xpow++) {
|
||||
for(let xmulti = 1; xmulti < 10; xmulti++) {
|
||||
|
@ -303,7 +299,7 @@ class CanvasAPI extends Module {
|
|||
* @private
|
||||
*/
|
||||
_drawAxes() {
|
||||
this.#ctx.strokeStyle = "#000000"
|
||||
this._ctx.strokeStyle = "#000000"
|
||||
let axisypos = this.logscalex ? 1 : 0
|
||||
this.drawXLine(axisypos)
|
||||
this.drawYLine(0)
|
||||
|
@ -324,19 +320,19 @@ class CanvasAPI extends Module {
|
|||
let axisypx = this.x2px(this.logscalex ? 1 : 0) // X coordinate of Y axis
|
||||
let axisxpx = this.y2px(0) // Y coordinate of X axis
|
||||
// Labels
|
||||
this.#ctx.fillStyle = "#000000"
|
||||
this.#ctx.font = `${this.textsize}px sans-serif`
|
||||
this.#ctx.fillText(this.ylabel, axisypx + 10, 24)
|
||||
let textWidth = this.#ctx.measureText(this.xlabel).width
|
||||
this.#ctx.fillText(this.xlabel, this.width - 14 - textWidth, axisxpx - 5)
|
||||
this._ctx.fillStyle = "#000000"
|
||||
this._ctx.font = `${this.textsize}px sans-serif`
|
||||
this._ctx.fillText(this.ylabel, axisypx + 10, 24)
|
||||
let textWidth = this._ctx.measureText(this.xlabel).width
|
||||
this._ctx.fillText(this.xlabel, this.width - 14 - textWidth, axisxpx - 5)
|
||||
// Axis graduation labels
|
||||
this.#ctx.font = `${this.textsize - 4}px sans-serif`
|
||||
this._ctx.font = `${this.textsize - 4}px sans-serif`
|
||||
|
||||
let txtMinus = this.#ctx.measureText("-").width
|
||||
let txtMinus = this._ctx.measureText("-").width
|
||||
if(this.showxgrad) {
|
||||
if(this.logscalex) {
|
||||
for(let xpow = -this.maxgradx; xpow <= this.maxgradx; xpow += 1) {
|
||||
textWidth = this.#ctx.measureText("10" + textsup(xpow)).width
|
||||
textWidth = this._ctx.measureText("10" + textsup(xpow)).width
|
||||
if(xpow !== 0)
|
||||
this.drawVisibleText("10" + textsup(xpow), this.x2px(Math.pow(10, xpow)) - textWidth / 2, axisxpx + 16 + (6 * (xpow === 1)))
|
||||
}
|
||||
|
@ -354,13 +350,13 @@ class CanvasAPI extends Module {
|
|||
for(let y = 0; y < this.axesSteps.y.maxDraw; y += 1) {
|
||||
let drawY = y * this.axesSteps.y.value
|
||||
let txtY = this.axesSteps.y.expression.simplify(y).toString().replace(/^\((.+)\)$/, "$1")
|
||||
textWidth = this.#ctx.measureText(txtY).width
|
||||
textWidth = this._ctx.measureText(txtY).width
|
||||
this.drawVisibleText(txtY, axisypx - 6 - textWidth, this.y2px(drawY) + 4 + (10 * (y === 0)))
|
||||
if(y !== 0)
|
||||
this.drawVisibleText("-" + txtY, axisypx - 6 - textWidth - txtMinus, this.y2px(-drawY) + 4)
|
||||
}
|
||||
}
|
||||
this.#ctx.fillStyle = "#FFFFFF"
|
||||
this._ctx.fillStyle = "#FFFFFF"
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -398,7 +394,7 @@ class CanvasAPI extends Module {
|
|||
drawVisibleText(text, x, y) {
|
||||
if(x > 0 && x < this.width && y > 0 && y < this.height) {
|
||||
text.toString().split("\n").forEach((txt, i) => {
|
||||
this.#ctx.fillText(txt, x, y + (this.textsize * i))
|
||||
this._ctx.fillText(txt, x, y + (this.textsize * i))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -413,8 +409,8 @@ class CanvasAPI extends Module {
|
|||
* @param {number} height
|
||||
*/
|
||||
drawVisibleImage(image, x, y, width, height) {
|
||||
this.#canvas.markDirty(Qt.rect(x, y, width, height))
|
||||
this.#ctx.drawImage(image, x, y, width, height)
|
||||
this._canvas.markDirty(Qt.rect(x, y, width, height))
|
||||
this._ctx.drawImage(image, x, y, width, height)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -428,7 +424,7 @@ class CanvasAPI extends Module {
|
|||
let defaultHeight = this.textsize * 1.2 // Approximate but good enough!
|
||||
for(let txt of text.split("\n")) {
|
||||
theight += defaultHeight
|
||||
if(this.#ctx.measureText(txt).width > twidth) twidth = this.#ctx.measureText(txt).width
|
||||
if(this._ctx.measureText(txt).width > twidth) twidth = this._ctx.measureText(txt).width
|
||||
}
|
||||
return { "width": twidth, "height": theight }
|
||||
}
|
||||
|
@ -498,10 +494,10 @@ class CanvasAPI extends Module {
|
|||
* @param {number} y2
|
||||
*/
|
||||
drawLine(x1, y1, x2, y2) {
|
||||
this.#ctx.beginPath()
|
||||
this.#ctx.moveTo(x1, y1)
|
||||
this.#ctx.lineTo(x2, y2)
|
||||
this.#ctx.stroke()
|
||||
this._ctx.beginPath()
|
||||
this._ctx.moveTo(x1, y1)
|
||||
this._ctx.lineTo(x2, y2)
|
||||
this._ctx.stroke()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -513,9 +509,9 @@ class CanvasAPI extends Module {
|
|||
* @param {number} dashPxSize
|
||||
*/
|
||||
drawDashedLine(x1, y1, x2, y2, dashPxSize = 6) {
|
||||
this.#ctx.setLineDash([dashPxSize / 2, dashPxSize])
|
||||
this._ctx.setLineDash([dashPxSize / 2, dashPxSize])
|
||||
this.drawLine(x1, y1, x2, y2)
|
||||
this.#ctx.setLineDash([])
|
||||
this._ctx.setLineDash([])
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -525,22 +521,14 @@ class CanvasAPI extends Module {
|
|||
* @param {function(LatexRenderResult|{width: number, height: number, source: string})} callback
|
||||
*/
|
||||
renderLatexImage(ltxText, color, callback) {
|
||||
const currentRedrawCount = this.#redrawCount
|
||||
const onRendered = (imgData) => {
|
||||
if(!this.#canvas.isImageLoaded(imgData.source) && !this.#canvas.isImageLoading(imgData.source)) {
|
||||
if(!this._canvas.isImageLoaded(imgData.source) && !this._canvas.isImageLoading(imgData.source)) {
|
||||
// Wait until the image is loaded to callback.
|
||||
this.#canvas.loadImageAsync(imgData.source).then(() => {
|
||||
if(this.#redrawCount === currentRedrawCount)
|
||||
callback(imgData)
|
||||
else
|
||||
console.log("1. Discard render of", imgData.source, this.#redrawCount, currentRedrawCount)
|
||||
})
|
||||
this._canvas.loadImage(imgData.source)
|
||||
this._canvas.imageLoaders[imgData.source] = [callback, imgData]
|
||||
} else {
|
||||
// Callback directly
|
||||
if(this.#redrawCount === currentRedrawCount)
|
||||
callback(imgData)
|
||||
else
|
||||
console.log("2. Discard render of", imgData.source, this.#redrawCount, currentRedrawCount)
|
||||
callback(imgData)
|
||||
}
|
||||
}
|
||||
const prerendered = Latex.findPrerendered(ltxText, this.textsize, color)
|
||||
|
@ -555,11 +543,11 @@ class CanvasAPI extends Module {
|
|||
//
|
||||
|
||||
get font() {
|
||||
return this.#ctx.font
|
||||
return this._ctx.font
|
||||
}
|
||||
|
||||
set font(value) {
|
||||
return this.#ctx.font = value
|
||||
return this._ctx.font = value
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -572,9 +560,9 @@ class CanvasAPI extends Module {
|
|||
* @param {boolean} counterclockwise
|
||||
*/
|
||||
arc(x, y, radius, startAngle, endAngle, counterclockwise = false) {
|
||||
this.#ctx.beginPath()
|
||||
this.#ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise)
|
||||
this.#ctx.stroke()
|
||||
this._ctx.beginPath()
|
||||
this._ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise)
|
||||
this._ctx.stroke()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -584,9 +572,9 @@ class CanvasAPI extends Module {
|
|||
* @param {number} radius
|
||||
*/
|
||||
disc(x, y, radius) {
|
||||
this.#ctx.beginPath()
|
||||
this.#ctx.arc(x, y, radius, 0, 2 * Math.PI)
|
||||
this.#ctx.fill()
|
||||
this._ctx.beginPath()
|
||||
this._ctx.arc(x, y, radius, 0, 2 * Math.PI)
|
||||
this._ctx.fill()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -597,7 +585,7 @@ class CanvasAPI extends Module {
|
|||
* @param {number} h
|
||||
*/
|
||||
fillRect(x, y, w, h) {
|
||||
this.#ctx.fillRect(x, y, w, h)
|
||||
this._ctx.fillRect(x, y, w, h)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
*/
|
||||
|
||||
import { Interface } from "./interface.mjs"
|
||||
import { BaseEventEmitter } from "../events.mjs"
|
||||
|
||||
// Define Modules interface before they are imported.
|
||||
globalThis.Modules = globalThis.Modules || {}
|
||||
|
@ -25,13 +24,7 @@ globalThis.Modules = globalThis.Modules || {}
|
|||
/**
|
||||
* Base class for global APIs in runtime.
|
||||
*/
|
||||
export class Module extends BaseEventEmitter {
|
||||
/** @type {string} */
|
||||
#name
|
||||
/** @type {Object.<string, (Interface|string|number|boolean)>} */
|
||||
#initializationParameters
|
||||
/** @type {boolean} */
|
||||
#initialized = false
|
||||
export class Module {
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -39,18 +32,11 @@ export class Module extends BaseEventEmitter {
|
|||
* @param {Object.<string, (Interface|string|number|boolean)>} initializationParameters - List of parameters for the initialize function.
|
||||
*/
|
||||
constructor(name, initializationParameters = {}) {
|
||||
super()
|
||||
console.log(`Loading module ${name}...`)
|
||||
this.#name = name
|
||||
this.#initializationParameters = initializationParameters
|
||||
}
|
||||
this.__name = name
|
||||
this.__initializationParameters = initializationParameters
|
||||
this.initialized = false
|
||||
|
||||
get name() {
|
||||
return this.#name;
|
||||
}
|
||||
|
||||
get initialized() {
|
||||
return this.#initialized
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -58,17 +44,17 @@ export class Module extends BaseEventEmitter {
|
|||
* @param {Object.<string, any>} options
|
||||
*/
|
||||
initialize(options) {
|
||||
if(this.#initialized)
|
||||
throw new Error(`Cannot reinitialize module ${this.#name}.`)
|
||||
console.log(`Initializing ${this.#name}...`)
|
||||
for(const [name, value] of Object.entries(this.#initializationParameters)) {
|
||||
if(this.initialized)
|
||||
throw new Error(`Cannot reinitialize module ${this.__name}.`)
|
||||
console.log(`Initializing ${this.__name}...`)
|
||||
for(const [name, value] of Object.entries(this.__initializationParameters)) {
|
||||
if(!options.hasOwnProperty(name))
|
||||
throw new Error(`Option '${name}' of initialize of module ${this.#name} does not exist.`)
|
||||
throw new Error(`Option '${name}' of initialize of module ${this.__name} does not exist.`)
|
||||
if(typeof value === "function" && value.prototype instanceof Interface)
|
||||
Interface.checkImplementation(value, options[name])
|
||||
Interface.check_implementation(value, options[name])
|
||||
else if(typeof value !== typeof options[name])
|
||||
throw new Error(`Option '${name}' of initialize of module ${this.#name} is not a '${value}' (${typeof options[name]}).`)
|
||||
throw new Error(`Option '${name}' of initialize of module ${this.__name} is not a '${value}' (${typeof options[name]}).`)
|
||||
}
|
||||
this.#initialized = true
|
||||
this.initialized = true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
import { Module } from "./common.mjs"
|
||||
import { Parser } from "../lib/expr-eval/parser.mjs"
|
||||
|
||||
const EVAL_VARIABLES = {
|
||||
const evalVariables = {
|
||||
// Variables not provided by expr-eval.js, needs to be provided manually
|
||||
"pi": Math.PI,
|
||||
"PI": Math.PI,
|
||||
|
@ -35,17 +35,15 @@ const EVAL_VARIABLES = {
|
|||
}
|
||||
|
||||
class ExprParserAPI extends Module {
|
||||
#parser = new Parser()
|
||||
|
||||
constructor() {
|
||||
super("ExprParser")
|
||||
this.currentVars = {}
|
||||
this.#parser = new Parser()
|
||||
this._parser = new Parser()
|
||||
|
||||
this.#parser.consts = Object.assign({}, this.#parser.consts, EVAL_VARIABLES)
|
||||
this._parser.consts = Object.assign({}, this._parser.consts, evalVariables)
|
||||
|
||||
this.#parser.functions.integral = this.integral.bind(this)
|
||||
this.#parser.functions.derivative = this.derivative.bind(this)
|
||||
this._parser.functions.integral = this.integral.bind(this)
|
||||
this._parser.functions.derivative = this.derivative.bind(this)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -70,7 +68,7 @@ class ExprParserAPI extends Module {
|
|||
[f, variable] = args
|
||||
if(typeof f !== "string" || typeof variable !== "string")
|
||||
throw EvalError(qsTranslate("usage", "Usage:\n%1").arg(usage2))
|
||||
f = this.#parser.parse(f).toJSFunction(variable, this.currentVars)
|
||||
f = this._parser.parse(f).toJSFunction(variable, this.currentVars)
|
||||
} else
|
||||
throw EvalError(qsTranslate("usage", "Usage:\n%1\n%2").arg(usage1).arg(usage2))
|
||||
return f
|
||||
|
@ -81,14 +79,14 @@ class ExprParserAPI extends Module {
|
|||
* @returns {ExprEvalExpression}
|
||||
*/
|
||||
parse(expression) {
|
||||
return this.#parser.parse(expression)
|
||||
return this._parser.parse(expression)
|
||||
}
|
||||
|
||||
integral(a = null, b = null, ...args) {
|
||||
integral(a, b, ...args) {
|
||||
let usage1 = qsTranslate("usage", "integral(<from: number>, <to: number>, <f: ExecutableObject>)")
|
||||
let usage2 = qsTranslate("usage", "integral(<from: number>, <to: number>, <f: string>, <variable: string>)")
|
||||
let f = this.parseArgumentsForFunction(args, usage1, usage2)
|
||||
if(typeof a !== "number" || typeof b !== "number")
|
||||
if(a == null || b == null)
|
||||
throw EvalError(qsTranslate("usage", "Usage:\n%1\n%2").arg(usage1).arg(usage2))
|
||||
|
||||
// https://en.wikipedia.org/wiki/Simpson%27s_rule
|
||||
|
@ -101,10 +99,10 @@ class ExprParserAPI extends Module {
|
|||
let usage2 = qsTranslate("usage", "derivative(<f: string>, <variable: string>, <x: number>)")
|
||||
let x = args.pop()
|
||||
let f = this.parseArgumentsForFunction(args, usage1, usage2)
|
||||
if(typeof x !== "number")
|
||||
if(x == null)
|
||||
throw EvalError(qsTranslate("usage", "Usage:\n%1\n%2").arg(usage1).arg(usage2))
|
||||
|
||||
let derivative_precision = 1e-8
|
||||
let derivative_precision = x / 10
|
||||
return (f(x + derivative_precision / 2) - f(x - derivative_precision / 2)) / derivative_precision
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,164 +17,60 @@
|
|||
*/
|
||||
|
||||
import { Module } from "./common.mjs"
|
||||
import { HelperInterface, NUMBER, STRING } from "./interface.mjs"
|
||||
import { BaseEvent } from "../events.mjs"
|
||||
import { Action, Actions } from "../history/index.mjs"
|
||||
import { HistoryInterface, NUMBER, STRING } from "./interface.mjs"
|
||||
|
||||
|
||||
|
||||
class ClearedEvent extends BaseEvent {
|
||||
constructor() {
|
||||
super("cleared")
|
||||
}
|
||||
}
|
||||
|
||||
class LoadedEvent extends BaseEvent {
|
||||
constructor() {
|
||||
super("loaded")
|
||||
}
|
||||
}
|
||||
|
||||
class AddedEvent extends BaseEvent {
|
||||
constructor(action) {
|
||||
super("added")
|
||||
this.action = action
|
||||
}
|
||||
}
|
||||
|
||||
class UndoneEvent extends BaseEvent {
|
||||
constructor(action) {
|
||||
super("undone")
|
||||
this.undid = action
|
||||
}
|
||||
}
|
||||
|
||||
class RedoneEvent extends BaseEvent {
|
||||
constructor(action) {
|
||||
super("redone")
|
||||
this.redid = action
|
||||
}
|
||||
}
|
||||
|
||||
class HistoryAPI extends Module {
|
||||
static emits = ["cleared", "loaded", "added", "undone", "redone"]
|
||||
|
||||
#helper
|
||||
|
||||
constructor() {
|
||||
super("History", {
|
||||
helper: HelperInterface,
|
||||
historyObj: HistoryInterface,
|
||||
themeTextColor: STRING,
|
||||
imageDepth: NUMBER,
|
||||
fontSize: NUMBER
|
||||
})
|
||||
// History QML object
|
||||
/** @type {Action[]} */
|
||||
this.undoStack = []
|
||||
/** @type {Action[]} */
|
||||
this.redoStack = []
|
||||
|
||||
this.history = null
|
||||
this.themeTextColor = "#FF0000"
|
||||
this.imageDepth = 2
|
||||
this.fontSize = 28
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HelperInterface} historyObj
|
||||
* @param {string} themeTextColor
|
||||
* @param {number} imageDepth
|
||||
* @param {number} fontSize
|
||||
*/
|
||||
initialize({ helper, themeTextColor, imageDepth, fontSize }) {
|
||||
super.initialize({ helper, themeTextColor, imageDepth, fontSize })
|
||||
this.#helper = helper
|
||||
initialize({ historyObj, themeTextColor, imageDepth, fontSize }) {
|
||||
super.initialize({ historyObj, themeTextColor, imageDepth, fontSize })
|
||||
this.history = historyObj
|
||||
this.themeTextColor = themeTextColor
|
||||
this.imageDepth = imageDepth
|
||||
this.fontSize = fontSize
|
||||
}
|
||||
|
||||
/**
|
||||
* Undoes the Action at the top of the undo stack and pushes it to the top of the redo stack.
|
||||
*/
|
||||
undo() {
|
||||
if(!this.initialized) throw new Error("Attempting undo before initialize!")
|
||||
if(this.undoStack.length > 0) {
|
||||
const action = this.undoStack.pop()
|
||||
action.undo()
|
||||
this.redoStack.push(action)
|
||||
this.emit(new UndoneEvent(action))
|
||||
}
|
||||
this.history.undo()
|
||||
}
|
||||
|
||||
/**
|
||||
* Redoes the Action at the top of the redo stack and pushes it to the top of the undo stack.
|
||||
*/
|
||||
redo() {
|
||||
if(!this.initialized) throw new Error("Attempting redo before initialize!")
|
||||
if(this.redoStack.length > 0) {
|
||||
const action = this.redoStack.pop()
|
||||
action.redo()
|
||||
this.undoStack.push(action)
|
||||
this.emit(new RedoneEvent(action))
|
||||
}
|
||||
this.history.redo()
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears both undo and redo stacks completely.
|
||||
*/
|
||||
clear() {
|
||||
if(!this.initialized) throw new Error("Attempting clear before initialize!")
|
||||
this.undoStack = []
|
||||
this.redoStack = []
|
||||
this.emit(new ClearedEvent())
|
||||
this.history.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an instance of HistoryLib.Action to history.
|
||||
* @param action
|
||||
*/
|
||||
addToHistory(action) {
|
||||
if(!this.initialized) throw new Error("Attempting addToHistory before initialize!")
|
||||
if(action instanceof Action) {
|
||||
console.log("Added new entry to history: " + action.getReadableString())
|
||||
this.undoStack.push(action)
|
||||
if(this.#helper.getSetting("reset_redo_stack"))
|
||||
this.redoStack = []
|
||||
this.emit(new AddedEvent(action))
|
||||
}
|
||||
this.history.addToHistory(action)
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserializes both the undo stack and redo stack from serialized content.
|
||||
* @param {[string, any[]][]} undoSt
|
||||
* @param {[string, any[]][]} redoSt
|
||||
*/
|
||||
unserialize(undoSt, redoSt) {
|
||||
unserialize(...data) {
|
||||
if(!this.initialized) throw new Error("Attempting unserialize before initialize!")
|
||||
this.clear()
|
||||
for(const [name, args] of undoSt)
|
||||
this.undoStack.push(
|
||||
new Actions[name](...args)
|
||||
)
|
||||
for(const [name, args] of redoSt)
|
||||
this.redoStack.push(
|
||||
new Actions[name](...args)
|
||||
)
|
||||
this.emit(new LoadedEvent())
|
||||
this.history.unserialize(...data)
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes history into JSON-able content.
|
||||
* @return {[[string, any[]], [string, any[]]]}
|
||||
*/
|
||||
serialize() {
|
||||
if(!this.initialized) throw new Error("Attempting serialize before initialize!")
|
||||
let undoSt = [], redoSt = [];
|
||||
for(const action of this.undoStack)
|
||||
undoSt.push([ action.type(), action.export() ])
|
||||
for(const action of this.redoStack)
|
||||
redoSt.push([ action.type(), action.export() ])
|
||||
return [undoSt, redoSt]
|
||||
return this.history.serialize()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
*/
|
||||
|
||||
import Objects from "./objects.mjs"
|
||||
import Settings from "./settings.mjs"
|
||||
import ExprParser from "./expreval.mjs"
|
||||
import Latex from "./latex.mjs"
|
||||
import History from "./history.mjs"
|
||||
|
@ -27,7 +26,6 @@ import Preferences from "./preferences.mjs"
|
|||
|
||||
export default {
|
||||
Objects,
|
||||
Settings,
|
||||
ExprParser,
|
||||
Latex,
|
||||
History,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/**
|
||||
/*!
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
*
|
||||
* @author Ad5001 <mail@ad5001.eu>
|
||||
|
@ -35,8 +35,9 @@ export class Interface {
|
|||
* Throws an error if the implementation does not conform to the interface.
|
||||
* @param {typeof Interface} interface_
|
||||
* @param {object} classToCheck
|
||||
* @return {boolean}
|
||||
*/
|
||||
static checkImplementation(interface_, classToCheck) {
|
||||
static check_implementation(interface_, classToCheck) {
|
||||
const properties = new interface_()
|
||||
const interfaceName = interface_.name
|
||||
const toCheckName = classToCheck.constructor.name
|
||||
|
@ -51,7 +52,7 @@ export class Interface {
|
|||
else if((typeof value) === "object")
|
||||
// Test type of object.
|
||||
if(value instanceof Interface)
|
||||
Interface.checkImplementation(value, classToCheck[property])
|
||||
Interface.check_implementation(value, classToCheck[property])
|
||||
else if(value.prototype && !(classToCheck[property] instanceof value))
|
||||
throw new Error(`Property '${property}' of ${interfaceName} implementation ${toCheckName} is not '${value.constructor.name}'.`)
|
||||
}
|
||||
|
@ -59,13 +60,32 @@ export class Interface {
|
|||
}
|
||||
|
||||
|
||||
export class CanvasInterface extends Interface {
|
||||
export class SettingsInterface extends Interface {
|
||||
width = NUMBER
|
||||
height = NUMBER
|
||||
xmin = NUMBER
|
||||
ymax = NUMBER
|
||||
xzoom = NUMBER
|
||||
yzoom = NUMBER
|
||||
xaxisstep = STRING
|
||||
yaxisstep = STRING
|
||||
xlabel = STRING
|
||||
ylabel = STRING
|
||||
linewidth = NUMBER
|
||||
textsize = NUMBER
|
||||
logscalex = BOOLEAN
|
||||
showxgrad = BOOLEAN
|
||||
showygrad = BOOLEAN
|
||||
}
|
||||
|
||||
export class CanvasInterface extends SettingsInterface {
|
||||
imageLoaders = OBJECT
|
||||
/** @type {function(string): CanvasRenderingContext2D} */
|
||||
getContext = FUNCTION
|
||||
/** @type {function(rect)} */
|
||||
markDirty = FUNCTION
|
||||
/** @type {function(string): Promise} */
|
||||
loadImageAsync = FUNCTION
|
||||
/** @type {function(string)} */
|
||||
loadImage = FUNCTION
|
||||
/** @type {function(string)} */
|
||||
isImageLoading = FUNCTION
|
||||
/** @type {function(string)} */
|
||||
|
@ -77,28 +97,30 @@ export class CanvasInterface extends Interface {
|
|||
export class RootInterface extends Interface {
|
||||
width = NUMBER
|
||||
height = NUMBER
|
||||
updateObjectsLists = FUNCTION
|
||||
}
|
||||
|
||||
export class DialogInterface extends Interface {
|
||||
show = FUNCTION
|
||||
}
|
||||
|
||||
export class HistoryInterface extends Interface {
|
||||
undo = FUNCTION
|
||||
redo = FUNCTION
|
||||
clear = FUNCTION
|
||||
addToHistory = FUNCTION
|
||||
unserialize = FUNCTION
|
||||
serialize = FUNCTION
|
||||
}
|
||||
|
||||
export class LatexInterface extends Interface {
|
||||
supportsAsyncRender = BOOLEAN
|
||||
/**
|
||||
* @param {string} markup - LaTeX markup to render
|
||||
* @param {number} fontSize - Font size (in pt) to render
|
||||
* @param {string} color - Color of the text to render
|
||||
* @returns {string} - Comma separated data of the image (source, width, height)
|
||||
*/
|
||||
renderSync = FUNCTION
|
||||
/**
|
||||
* @param {string} markup - LaTeX markup to render
|
||||
* @param {number} fontSize - Font size (in pt) to render
|
||||
* @param {string} color - Color of the text to render
|
||||
* @returns {Promise<string>} - Comma separated data of the image (source, width, height)
|
||||
*/
|
||||
renderAsync = FUNCTION
|
||||
render = FUNCTION
|
||||
/**
|
||||
* @param {string} markup - LaTeX markup to render
|
||||
* @param {number} fontSize - Font size (in pt) to render
|
||||
|
@ -117,13 +139,37 @@ export class HelperInterface extends Interface {
|
|||
/**
|
||||
* Gets a setting from the config
|
||||
* @param {string} settingName - Setting (and its dot-separated namespace) to get (e.g. "default_graph.xmin")
|
||||
* @returns {string|number|boolean} Value of the setting
|
||||
* @returns {boolean} Value of the setting
|
||||
*/
|
||||
getSettingBool = FUNCTION
|
||||
/**
|
||||
* Gets a setting from the config
|
||||
* @param {string} settingName - Setting (and its dot-separated namespace) to get (e.g. "default_graph.xmin")
|
||||
* @returns {number} Value of the setting
|
||||
*/
|
||||
getSettingInt = FUNCTION
|
||||
/**
|
||||
* Gets a setting from the config
|
||||
* @param {string} settingName - Setting (and its dot-separated namespace) to get (e.g. "default_graph.xmin")
|
||||
* @returns {string} Value of the setting
|
||||
*/
|
||||
getSetting = FUNCTION
|
||||
/**
|
||||
* Sets a setting in the config
|
||||
* @param {string} settingName - Setting (and its dot-separated namespace) to set (e.g. "default_graph.xmin")
|
||||
* @param {string|number|boolean} value
|
||||
* @param {boolean} value
|
||||
*/
|
||||
setSettingBool = FUNCTION
|
||||
/**
|
||||
* Sets a setting in the config
|
||||
* @param {string} settingName - Setting (and its dot-separated namespace) to set (e.g. "default_graph.xmin")
|
||||
* @param {number} value
|
||||
*/
|
||||
setSettingInt = FUNCTION
|
||||
/**
|
||||
* Sets a setting in the config
|
||||
* @param {string} settingName - Setting (and its dot-separated namespace) to set (e.g. "default_graph.xmin")
|
||||
* @param {string} value
|
||||
*/
|
||||
setSetting = FUNCTION
|
||||
/**
|
||||
|
@ -138,4 +184,4 @@ export class HelperInterface extends Interface {
|
|||
* @returns {string} the loaded data - just JSON encoded, requires the "LPFv1" mime to be stripped
|
||||
*/
|
||||
load = FUNCTION
|
||||
}
|
||||
}
|
|
@ -19,69 +19,36 @@
|
|||
import { Module } from "./common.mjs"
|
||||
import Objects from "./objects.mjs"
|
||||
import History from "./history.mjs"
|
||||
import Settings from "./settings.mjs"
|
||||
import { DialogInterface, RootInterface } from "./interface.mjs"
|
||||
import { BaseEvent } from "../events.mjs"
|
||||
import Canvas from "./canvas.mjs"
|
||||
import { DialogInterface, RootInterface, SettingsInterface } from "./interface.mjs"
|
||||
|
||||
|
||||
class LoadedEvent extends BaseEvent {
|
||||
constructor() {
|
||||
super("loaded")
|
||||
}
|
||||
}
|
||||
|
||||
class SavedEvent extends BaseEvent {
|
||||
constructor() {
|
||||
super("saved")
|
||||
}
|
||||
}
|
||||
|
||||
class ModifiedEvent extends BaseEvent {
|
||||
constructor() {
|
||||
super("modified")
|
||||
}
|
||||
}
|
||||
|
||||
class IOAPI extends Module {
|
||||
static emits = ["loaded", "saved", "modified"]
|
||||
|
||||
/** @type {RootInterface} */
|
||||
#rootElement
|
||||
/** @type {{show: function(string)}} */
|
||||
#alert
|
||||
#saved = true
|
||||
|
||||
constructor() {
|
||||
super("IO", {
|
||||
alert: DialogInterface,
|
||||
root: RootInterface
|
||||
root: RootInterface,
|
||||
settings: SettingsInterface
|
||||
})
|
||||
|
||||
// Settings.on("changed", this.__emitModified.bind(this))
|
||||
History.on("added undone redone", this.__emitModified.bind(this))
|
||||
/**
|
||||
* Path of the currently opened file. Empty if no file is opened.
|
||||
* @type {string}
|
||||
*/
|
||||
this.saveFileName = ""
|
||||
}
|
||||
|
||||
__emitModified() {
|
||||
this.#saved = false
|
||||
this.emit(new ModifiedEvent())
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* True if no changes have been made since last save, false otherwise.
|
||||
* @return {boolean}
|
||||
*/
|
||||
get saved() { return this.#saved }
|
||||
|
||||
/**
|
||||
* Initializes module with QML elements.
|
||||
* @param {RootInterface} root
|
||||
* @param {SettingsInterface} settings
|
||||
* @param {{show: function(string)}} alert
|
||||
*/
|
||||
initialize({ root, alert }) {
|
||||
super.initialize({ root, alert })
|
||||
this.#rootElement = root
|
||||
this.#alert = alert
|
||||
initialize({ root, settings, alert }) {
|
||||
super.initialize({ root, settings, alert })
|
||||
this.rootElement = root
|
||||
this.settings = settings
|
||||
this.alert = alert
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -93,7 +60,7 @@ class IOAPI extends Module {
|
|||
// Add extension if necessary
|
||||
if(["lpf"].indexOf(filename.split(".")[filename.split(".").length - 1]) === -1)
|
||||
filename += ".lpf"
|
||||
Settings.set("saveFilename", filename, false)
|
||||
this.saveFilename = filename
|
||||
let objs = {}
|
||||
for(let objType in Objects.currentObjects) {
|
||||
objs[objType] = []
|
||||
|
@ -102,29 +69,28 @@ class IOAPI extends Module {
|
|||
}
|
||||
}
|
||||
let settings = {
|
||||
"xzoom": Settings.xzoom,
|
||||
"yzoom": Settings.yzoom,
|
||||
"xmin": Settings.xmin,
|
||||
"ymax": Settings.ymax,
|
||||
"xaxisstep": Settings.xaxisstep,
|
||||
"yaxisstep": Settings.yaxisstep,
|
||||
"xaxislabel": Settings.xlabel,
|
||||
"yaxislabel": Settings.ylabel,
|
||||
"logscalex": Settings.logscalex,
|
||||
"linewidth": Settings.linewidth,
|
||||
"showxgrad": Settings.showxgrad,
|
||||
"showygrad": Settings.showygrad,
|
||||
"textsize": Settings.textsize,
|
||||
"xzoom": this.settings.xzoom,
|
||||
"yzoom": this.settings.yzoom,
|
||||
"xmin": this.settings.xmin,
|
||||
"ymax": this.settings.ymax,
|
||||
"xaxisstep": this.settings.xaxisstep,
|
||||
"yaxisstep": this.settings.yaxisstep,
|
||||
"xaxislabel": this.settings.xlabel,
|
||||
"yaxislabel": this.settings.ylabel,
|
||||
"logscalex": this.settings.logscalex,
|
||||
"linewidth": this.settings.linewidth,
|
||||
"showxgrad": this.settings.showxgrad,
|
||||
"showygrad": this.settings.showygrad,
|
||||
"textsize": this.settings.textsize,
|
||||
"history": History.serialize(),
|
||||
"width": this.#rootElement.width,
|
||||
"height": this.#rootElement.height,
|
||||
"width": this.rootElement.width,
|
||||
"height": this.rootElement.height,
|
||||
"objects": objs,
|
||||
"type": "logplotv1"
|
||||
}
|
||||
Helper.write(filename, JSON.stringify(settings))
|
||||
this.#alert.show(qsTranslate("io", "Saved plot to '%1'.").arg(filename.split("/").pop()))
|
||||
this.#saved = true
|
||||
this.emit(new SavedEvent())
|
||||
this.alert.show(qsTranslate("io", "Saved plot to '%1'.").arg(filename.split("/").pop()))
|
||||
History.history.saved = true
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -135,32 +101,32 @@ class IOAPI extends Module {
|
|||
if(!this.initialized) throw new Error("Attempting loadDiagram before initialize!")
|
||||
if(!History.initialized) throw new Error("Attempting loadDiagram before history is initialized!")
|
||||
let basename = filename.split("/").pop()
|
||||
this.#alert.show(qsTranslate("io", "Loading file '%1'.").arg(basename))
|
||||
this.alert.show(qsTranslate("io", "Loading file '%1'.").arg(basename))
|
||||
let data = JSON.parse(Helper.load(filename))
|
||||
let error = ""
|
||||
if(data.hasOwnProperty("type") && data["type"] === "logplotv1") {
|
||||
History.clear()
|
||||
// Importing settings
|
||||
Settings.set("saveFilename", filename, false)
|
||||
Settings.set("xzoom", parseFloat(data["xzoom"]) || 100, false)
|
||||
Settings.set("yzoom", parseFloat(data["yzoom"]) || 10, false)
|
||||
Settings.set("xmin", parseFloat(data["xmin"]) || 5 / 10, false)
|
||||
Settings.set("ymax", parseFloat(data["ymax"]) || 24, false)
|
||||
Settings.set("xaxisstep", data["xaxisstep"] || "4", false)
|
||||
Settings.set("yaxisstep", data["yaxisstep"] || "4", false)
|
||||
Settings.set("xlabel", data["xaxislabel"] || "", false)
|
||||
Settings.set("ylabel", data["yaxislabel"] || "", false)
|
||||
Settings.set("logscalex", data["logscalex"] === true, false)
|
||||
this.settings.saveFilename = filename
|
||||
this.settings.xzoom = parseFloat(data["xzoom"]) || 100
|
||||
this.settings.yzoom = parseFloat(data["yzoom"]) || 10
|
||||
this.settings.xmin = parseFloat(data["xmin"]) || 5 / 10
|
||||
this.settings.ymax = parseFloat(data["ymax"]) || 24
|
||||
this.settings.xaxisstep = data["xaxisstep"] || "4"
|
||||
this.settings.yaxisstep = data["yaxisstep"] || "4"
|
||||
this.settings.xlabel = data["xaxislabel"] || ""
|
||||
this.settings.ylabel = data["yaxislabel"] || ""
|
||||
this.settings.logscalex = data["logscalex"] === true
|
||||
if("showxgrad" in data)
|
||||
Settings.set("showxgrad", data["showxgrad"], false)
|
||||
this.settings.showxgrad = data["showxgrad"]
|
||||
if("showygrad" in data)
|
||||
Settings.set("showygrad", data["showygrad"], false)
|
||||
this.settings.textsize = data["showygrad"]
|
||||
if("linewidth" in data)
|
||||
Settings.set("linewidth", data["linewidth"], false)
|
||||
this.settings.linewidth = data["linewidth"]
|
||||
if("textsize" in data)
|
||||
Settings.set("textsize", data["textsize"], false)
|
||||
this.#rootElement.height = parseFloat(data["height"]) || 500
|
||||
this.#rootElement.width = parseFloat(data["width"]) || 1000
|
||||
this.settings.textsize = data["textsize"]
|
||||
this.rootElement.height = parseFloat(data["height"]) || 500
|
||||
this.rootElement.width = parseFloat(data["width"]) || 1000
|
||||
|
||||
// Importing objects
|
||||
Objects.currentObjects = {}
|
||||
|
@ -191,18 +157,20 @@ class IOAPI extends Module {
|
|||
if("history" in data)
|
||||
History.unserialize(...data["history"])
|
||||
|
||||
// Refreshing sidebar
|
||||
this.rootElement.updateObjectsLists()
|
||||
} else {
|
||||
error = qsTranslate("io", "Invalid file provided.")
|
||||
}
|
||||
if(error !== "") {
|
||||
console.log(error)
|
||||
this.#alert.show(qsTranslate("io", "Could not load file: ") + error)
|
||||
this.alert.show(qsTranslate("io", "Could not load file: ") + error)
|
||||
// TODO: Error handling
|
||||
return
|
||||
}
|
||||
this.#alert.show(qsTranslate("io", "Loaded file '%1'.").arg(basename))
|
||||
this.#saved = true
|
||||
this.emit(new LoadedEvent())
|
||||
Canvas.redraw()
|
||||
this.alert.show(qsTranslate("io", "Loaded file '%1'.").arg(basename))
|
||||
History.history.saved = true
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,12 +17,11 @@
|
|||
*/
|
||||
|
||||
import { Module } from "./common.mjs"
|
||||
import { BaseEvent } from "../events.mjs"
|
||||
import * as Instruction from "../lib/expr-eval/instruction.mjs"
|
||||
import { escapeValue } from "../lib/expr-eval/expression.mjs"
|
||||
import { HelperInterface, LatexInterface } from "./interface.mjs"
|
||||
|
||||
const unicodechars = ["pi", "∞",
|
||||
const unicodechars = [
|
||||
"α", "β", "γ", "δ", "ε", "ζ", "η",
|
||||
"π", "θ", "κ", "λ", "μ", "ξ", "ρ",
|
||||
"ς", "σ", "τ", "φ", "χ", "ψ", "ω",
|
||||
|
@ -31,9 +30,9 @@ const unicodechars = ["pi", "∞",
|
|||
"ₕ", "ₖ", "ₗ", "ₘ", "ₙ", "ₚ", "ₛ",
|
||||
"ₜ", "¹", "²", "³", "⁴", "⁵", "⁶",
|
||||
"⁷", "⁸", "⁹", "⁰", "₁", "₂", "₃",
|
||||
"₄", "₅", "₆", "₇", "₈", "₉", "₀"
|
||||
]
|
||||
const equivalchars = ["\\pi", "\\infty",
|
||||
"₄", "₅", "₆", "₇", "₈", "₉", "₀",
|
||||
"pi", "∞"]
|
||||
const equivalchars = [
|
||||
"\\alpha", "\\beta", "\\gamma", "\\delta", "\\epsilon", "\\zeta", "\\eta",
|
||||
"\\pi", "\\theta", "\\kappa", "\\lambda", "\\mu", "\\xi", "\\rho",
|
||||
"\\sigma", "\\sigma", "\\tau", "\\phi", "\\chi", "\\psi", "\\omega",
|
||||
|
@ -43,29 +42,7 @@ const equivalchars = ["\\pi", "\\infty",
|
|||
"{}_{t}", "{}^{1}", "{}^{2}", "{}^{3}", "{}^{4}", "{}^{5}", "{}^{6}",
|
||||
"{}^{7}", "{}^{8}", "{}^{9}", "{}^{0}", "{}_{1}", "{}_{2}", "{}_{3}",
|
||||
"{}_{4}", "{}_{5}", "{}_{6}", "{}_{7}", "{}_{8}", "{}_{9}", "{}_{0}",
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
class AsyncRenderStartedEvent extends BaseEvent {
|
||||
constructor(markup, fontSize, color) {
|
||||
super("async-render-started")
|
||||
this.markup = markup
|
||||
this.fontSize = fontSize
|
||||
this.color = color
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AsyncRenderFinishedEvent extends BaseEvent {
|
||||
constructor(markup, fontSize, color) {
|
||||
super("async-render-finished")
|
||||
this.markup = markup
|
||||
this.fontSize = fontSize
|
||||
this.color = color
|
||||
}
|
||||
}
|
||||
"\\pi", "\\infty"]
|
||||
|
||||
/**
|
||||
* Class containing the result of a LaTeX render.
|
||||
|
@ -83,11 +60,6 @@ class LatexRenderResult {
|
|||
}
|
||||
|
||||
class LatexAPI extends Module {
|
||||
static emits = ["async-render-started", "async-render-finished"]
|
||||
|
||||
/** @type {LatexInterface} */
|
||||
#latex = null
|
||||
|
||||
constructor() {
|
||||
super("Latex", {
|
||||
latex: LatexInterface,
|
||||
|
@ -97,7 +69,6 @@ class LatexAPI extends Module {
|
|||
* true if latex has been enabled by the user, false otherwise.
|
||||
*/
|
||||
this.enabled = false
|
||||
this.promises = new Set()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -106,8 +77,9 @@ class LatexAPI extends Module {
|
|||
*/
|
||||
initialize({ latex, helper }) {
|
||||
super.initialize({ latex, helper })
|
||||
this.#latex = latex
|
||||
this.enabled = helper.getSetting("enable_latex")
|
||||
this.latex = latex
|
||||
this.helper = helper
|
||||
this.enabled = helper.getSettingBool("enable_latex")
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -121,7 +93,7 @@ class LatexAPI extends Module {
|
|||
*/
|
||||
findPrerendered(markup, fontSize, color) {
|
||||
if(!this.initialized) throw new Error("Attempting findPrerendered before initialize!")
|
||||
const data = this.#latex.findPrerendered(markup, fontSize, color)
|
||||
const data = this.latex.findPrerendered(markup, fontSize, color)
|
||||
let ret = null
|
||||
if(data !== "")
|
||||
ret = new LatexRenderResult(...data.split(","))
|
||||
|
@ -138,19 +110,7 @@ class LatexAPI extends Module {
|
|||
*/
|
||||
async requestAsyncRender(markup, fontSize, color) {
|
||||
if(!this.initialized) throw new Error("Attempting requestAsyncRender before initialize!")
|
||||
let render
|
||||
if(this.#latex.supportsAsyncRender) {
|
||||
this.emit(new AsyncRenderStartedEvent(markup, fontSize, color))
|
||||
// Storing promise so that it does not get dereferenced.
|
||||
const promise = this.#latex.renderAsync(markup, fontSize, color)
|
||||
this.promises.add(promise)
|
||||
render = await promise
|
||||
this.promises.delete(promise)
|
||||
this.emit(new AsyncRenderFinishedEvent(markup, fontSize, color))
|
||||
} else {
|
||||
render = this.#latex.renderSync(markup, fontSize, color)
|
||||
}
|
||||
const args = render.split(",")
|
||||
let args = this.latex.render(markup, fontSize, color).split(",")
|
||||
return new LatexRenderResult(...args)
|
||||
}
|
||||
|
||||
|
@ -175,10 +135,9 @@ class LatexAPI extends Module {
|
|||
*/
|
||||
parif(elem, contents) {
|
||||
elem = elem.toString()
|
||||
const contains = contents.some(x => elem.indexOf(x) > 0)
|
||||
if(contains && (elem[0] !== "(" || elem.at(-1) !== ")"))
|
||||
if(elem[0] !== "(" && elem[elem.length - 1] !== ")" && contents.some(x => elem.indexOf(x) > 0))
|
||||
return this.par(elem)
|
||||
if(!contains && elem[0] === "(" && elem.at(-1) === ")")
|
||||
if(elem[0] === "(" && elem[elem.length - 1] === ")")
|
||||
return elem.removeEnclosure()
|
||||
return elem
|
||||
}
|
||||
|
@ -196,21 +155,20 @@ class LatexAPI extends Module {
|
|||
if(args.length === 3)
|
||||
return `\\frac{d${args[0].removeEnclosure().replaceAll(args[1].removeEnclosure(), "x")}}{dx}`
|
||||
else
|
||||
return `\\frac{d${args[0]}}{dx}(${args[1]})`
|
||||
return `\\frac{d${args[0]}}{dx}(x)`
|
||||
case "integral":
|
||||
if(args.length === 4)
|
||||
return `\\int\\limits_{${args[0]}}^{${args[1]}}${args[2].removeEnclosure()} d${args[3].removeEnclosure()}`
|
||||
else
|
||||
return `\\int\\limits_{${args[0]}}^{${args[1]}}${args[2]}(t) dt`
|
||||
case "sqrt":
|
||||
const arg = this.parif(args.join(", "), [])
|
||||
return `\\sqrt{${arg}}`
|
||||
return `\\sqrt\\left(${args.join(", ")}\\right)`
|
||||
case "abs":
|
||||
return `\\left|${args.join(", ")}\\right|`
|
||||
case "floor":
|
||||
return `\\left\\lfloor{${args.join(", ")}}\\right\\rfloor`
|
||||
return `\\left\\lfloor${args.join(", ")}\\right\\rfloor`
|
||||
case "ceil":
|
||||
return `\\left\\lceil{${args.join(", ")}}\\right\\rceil`
|
||||
return `\\left\\lceil${args.join(", ")}\\right\\rceil`
|
||||
default:
|
||||
return `\\mathrm{${f}}\\left(${args.join(", ")}\\right)`
|
||||
}
|
||||
|
@ -224,17 +182,16 @@ class LatexAPI extends Module {
|
|||
* @returns {string}
|
||||
*/
|
||||
variable(vari, wrapIn$ = false) {
|
||||
if(wrapIn$) {
|
||||
if(wrapIn$)
|
||||
for(let i = 0; i < unicodechars.length; i++) {
|
||||
if(vari.includes(unicodechars[i]))
|
||||
vari = vari.replaceAll(unicodechars[i], "$" + equivalchars[i] + "$")
|
||||
}
|
||||
} else {
|
||||
else
|
||||
for(let i = 0; i < unicodechars.length; i++) {
|
||||
if(vari.includes(unicodechars[i]))
|
||||
vari = vari.replaceAll(unicodechars[i], equivalchars[i])
|
||||
}
|
||||
}
|
||||
return vari
|
||||
}
|
||||
|
||||
|
@ -302,7 +259,7 @@ class LatexAPI extends Module {
|
|||
throw new EvalError("Unknown operator " + item.value + ".")
|
||||
}
|
||||
break
|
||||
case Instruction.IOP3: // Ternary operator
|
||||
case Instruction.IOP3: // Thirdiary operator
|
||||
n3 = nstack.pop()
|
||||
n2 = nstack.pop()
|
||||
n1 = nstack.pop()
|
||||
|
@ -329,7 +286,7 @@ class LatexAPI extends Module {
|
|||
nstack.push(this.parif(n1, ["+", "-", "*", "/", "^"]) + "!")
|
||||
break
|
||||
default:
|
||||
nstack.push(this.functionToLatex(f, [this.parif(n1, ["+", "-", "*", "/", "^"])]))
|
||||
nstack.push(f + this.parif(n1, ["+", "-", "*", "/", "^"]))
|
||||
break
|
||||
}
|
||||
break
|
||||
|
@ -364,6 +321,9 @@ class LatexAPI extends Module {
|
|||
throw new EvalError("invalid Expression")
|
||||
}
|
||||
}
|
||||
if(nstack.length > 1) {
|
||||
nstack = [nstack.join(";")]
|
||||
}
|
||||
return String(nstack[0])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,17 +17,13 @@
|
|||
*/
|
||||
|
||||
import { Module } from "./common.mjs"
|
||||
import { textsub } from "../utils/index.mjs"
|
||||
import { textsub } from "../utils.mjs"
|
||||
|
||||
class ObjectsAPI extends Module {
|
||||
|
||||
constructor() {
|
||||
super("Objects")
|
||||
|
||||
/**
|
||||
* List of object constructors.
|
||||
* @type {Object.<string,typeof DrawableObject>}
|
||||
*/
|
||||
this.types = {}
|
||||
/**
|
||||
* List of objects for each type of object.
|
||||
|
@ -69,7 +65,7 @@ class ObjectsAPI extends Module {
|
|||
* @param {string} newName - Name to rename the object to.
|
||||
*/
|
||||
renameObject(oldName, newName) {
|
||||
const obj = this.currentObjectsByName[oldName]
|
||||
let obj = this.currentObjectsByName[oldName]
|
||||
delete this.currentObjectsByName[oldName]
|
||||
this.currentObjectsByName[newName] = obj
|
||||
obj.name = newName
|
||||
|
@ -80,7 +76,7 @@ class ObjectsAPI extends Module {
|
|||
* @param {string} objName - Current name of the object.
|
||||
*/
|
||||
deleteObject(objName) {
|
||||
const obj = this.currentObjectsByName[objName]
|
||||
let obj = this.currentObjectsByName[objName]
|
||||
if(obj !== undefined) {
|
||||
this.currentObjects[obj.type].splice(this.currentObjects[obj.type].indexOf(obj), 1)
|
||||
obj.delete()
|
||||
|
|
|
@ -20,9 +20,6 @@ import General from "../preferences/general.mjs"
|
|||
import Editor from "../preferences/expression.mjs"
|
||||
import DefaultGraph from "../preferences/default.mjs"
|
||||
|
||||
/**
|
||||
* Module for application wide settings.
|
||||
*/
|
||||
class PreferencesAPI extends Module {
|
||||
constructor() {
|
||||
super("Preferences")
|
||||
|
|
|
@ -1,186 +0,0 @@
|
|||
/**
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
import { Module } from "./common.mjs"
|
||||
import { BaseEvent } from "../events.mjs"
|
||||
import { HelperInterface } from "./interface.mjs"
|
||||
|
||||
|
||||
/**
|
||||
* Base event for when a setting was changed.
|
||||
*/
|
||||
class ChangedEvent extends BaseEvent {
|
||||
/**
|
||||
*
|
||||
* @param {string} property - Name of the property that was chagned
|
||||
* @param {string|number|boolean} oldValue - Old value of the property
|
||||
* @param {string|number|boolean} newValue - Current (new) value of the property
|
||||
* @param {boolean} byUser - True if the user is at the source of the change in the setting.
|
||||
*/
|
||||
constructor(property, oldValue, newValue, byUser) {
|
||||
super("changed")
|
||||
|
||||
this.property = property
|
||||
this.oldValue = oldValue
|
||||
this.newValue = newValue
|
||||
this.byUser = byUser
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Module for graph settings.
|
||||
*/
|
||||
class SettingsAPI extends Module {
|
||||
static emits = ["changed"]
|
||||
|
||||
#nonConfigurable = ["saveFilename"]
|
||||
|
||||
/** @type {Map<string, string|number|boolean>} */
|
||||
#properties = new Map([
|
||||
["saveFilename", ""],
|
||||
["xzoom", 100],
|
||||
["yzoom", 10],
|
||||
["xmin", .5],
|
||||
["ymax", 25],
|
||||
["xaxisstep", "4"],
|
||||
["yaxisstep", "4"],
|
||||
["xlabel", ""],
|
||||
["ylabel", ""],
|
||||
["linewidth", 1],
|
||||
["textsize", 18],
|
||||
["logscalex", true],
|
||||
["showxgrad", true],
|
||||
["showygrad", true]
|
||||
])
|
||||
|
||||
constructor() {
|
||||
super("Settings", {
|
||||
helper: HelperInterface
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {HelperInterface} helper
|
||||
*/
|
||||
initialize({ helper }) {
|
||||
super.initialize({ helper })
|
||||
// Initialize default values.
|
||||
for(const key of this.#properties.keys())
|
||||
if(!this.#nonConfigurable.includes(key))
|
||||
this.set(key, helper.getSetting("default_graph."+key), false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a setting to a given value
|
||||
*
|
||||
* @param {string} property
|
||||
* @param {string|number|boolean} value
|
||||
* @param {boolean} byUser - Set to true if the user is at the origin of this change.
|
||||
*/
|
||||
set(property, value, byUser) {
|
||||
if(!this.#properties.has(property))
|
||||
throw new Error(`Property ${property} is not a setting.`)
|
||||
const oldValue = this.#properties.get(property)
|
||||
const propType = typeof oldValue
|
||||
if(byUser)
|
||||
console.debug("Setting", property, "from", oldValue, "to", value, `(${typeof value}, ${byUser})`)
|
||||
if(propType !== typeof value)
|
||||
throw new Error(`Value of ${property} must be a ${propType} (${typeof value} provided).`)
|
||||
this.#properties.set(property, value)
|
||||
const evt = new ChangedEvent(property, oldValue, value, byUser === true)
|
||||
this.emit(evt)
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of the currently opened file.
|
||||
* @returns {string}
|
||||
*/
|
||||
get saveFilename() { return this.#properties.get("saveFilename") }
|
||||
|
||||
/**
|
||||
* Zoom on the x axis of the diagram.
|
||||
* @returns {number}
|
||||
*/
|
||||
get xzoom() { return this.#properties.get("xzoom") }
|
||||
/**
|
||||
* Zoom on the y axis of the diagram.
|
||||
* @returns {number}
|
||||
*/
|
||||
get yzoom() { return this.#properties.get("yzoom") }
|
||||
/**
|
||||
* Minimum x of the diagram.
|
||||
* @returns {number}
|
||||
*/
|
||||
get xmin() { return this.#properties.get("xmin") }
|
||||
/**
|
||||
* Maximum y of the diagram.
|
||||
* @returns {number}
|
||||
*/
|
||||
get ymax() { return this.#properties.get("ymax") }
|
||||
/**
|
||||
* Step of the x axis graduation (expression).
|
||||
* @note Only available in non-logarithmic mode.
|
||||
* @returns {string}
|
||||
*/
|
||||
get xaxisstep() { return this.#properties.get("xaxisstep") }
|
||||
/**
|
||||
* Step of the y axis graduation (expression).
|
||||
* @returns {string}
|
||||
*/
|
||||
get yaxisstep() { return this.#properties.get("yaxisstep") }
|
||||
/**
|
||||
* Label used on the x axis.
|
||||
* @returns {string}
|
||||
*/
|
||||
get xlabel() { return this.#properties.get("xlabel") }
|
||||
/**
|
||||
* Label used on the y axis.
|
||||
* @returns {string}
|
||||
*/
|
||||
get ylabel() { return this.#properties.get("ylabel") }
|
||||
/**
|
||||
* Width of lines that will be drawn into the canvas.
|
||||
* @returns {number}
|
||||
*/
|
||||
get linewidth() { return this.#properties.get("linewidth") }
|
||||
/**
|
||||
* Font size of the text that will be drawn into the canvas.
|
||||
* @returns {number}
|
||||
*/
|
||||
get textsize() { return this.#properties.get("textsize") }
|
||||
/**
|
||||
* true if the canvas should be in logarithmic mode, false otherwise.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get logscalex() { return this.#properties.get("logscalex") }
|
||||
/**
|
||||
* true if the x graduation should be shown, false otherwise.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get showxgrad() { return this.#properties.get("showxgrad") }
|
||||
/**
|
||||
* true if the y graduation should be shown, false otherwise.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get showygrad() { return this.#properties.get("showygrad") }
|
||||
}
|
||||
|
||||
Modules.Settings = Modules.Settings || new SettingsAPI()
|
||||
export default Modules.Settings
|
||||
|
|
@ -63,7 +63,7 @@ export default class BodePhase extends ExecutableObject {
|
|||
// Create new point
|
||||
om_0 = Objects.createNewRegisteredObject("Point", [Objects.getNewName("ω"), this.color, "name"])
|
||||
om_0.labelPosition = this.phase.execute() >= 0 ? "above" : "below"
|
||||
History.addToHistory(new CreateNewObject(om_0.name, "Point", om_0.export()))
|
||||
History.history.addToHistory(new CreateNewObject(om_0.name, "Point", om_0.export()))
|
||||
labelPosition = "below"
|
||||
}
|
||||
om_0.requiredBy.push(this)
|
||||
|
|
|
@ -16,9 +16,9 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { getRandomColor } from "../utils.mjs"
|
||||
import Objects from "../module/objects.mjs"
|
||||
import Latex from "../module/latex.mjs"
|
||||
import { getRandomColor } from "../utils/index.mjs"
|
||||
import { ensureTypeSafety, serializesByPropertyType } from "../parameters.mjs"
|
||||
|
||||
// This file contains the default data to be imported from all other objects
|
||||
|
@ -224,7 +224,7 @@ export class DrawableObject {
|
|||
currentObjectsByName[objName].requiredBy.push(this)
|
||||
}
|
||||
}
|
||||
if(this[property].canBeCached && this[property].requiredObjects().length > 0)
|
||||
if(this[property].cached && this[property].requiredObjects().length > 0)
|
||||
// Recalculate
|
||||
this[property].recache()
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { textsub } from "../utils/index.mjs"
|
||||
import { textsub } from "../utils.mjs"
|
||||
import Objects from "../module/objects.mjs"
|
||||
import { ExecutableObject } from "./common.mjs"
|
||||
import { parseDomain, Expression, SpecialDomain } from "../math/index.mjs"
|
||||
|
@ -122,9 +122,7 @@ export default class Function extends ExecutableObject {
|
|||
*/
|
||||
static drawFunction(canvas, expr, definitionDomain, destinationDomain, drawPoints = true, drawDash = true) {
|
||||
let pxprecision = 10
|
||||
const startDrawFrom = canvas.x2px(1)%pxprecision-pxprecision
|
||||
let previousX = canvas.px2x(startDrawFrom)
|
||||
// console.log("Starting draw from", previousX, startDrawFrom, canvas.x2px(1))
|
||||
let previousX = canvas.px2x(0)
|
||||
let previousY = null
|
||||
if(definitionDomain instanceof SpecialDomain && definitionDomain.moveSupported) {
|
||||
// Point based functions.
|
||||
|
@ -162,7 +160,7 @@ export default class Function extends ExecutableObject {
|
|||
// Calculate the previousY at the start of the canvas
|
||||
if(definitionDomain.includes(previousX))
|
||||
previousY = expr.execute(previousX)
|
||||
for(let px = pxprecision; px-pxprecision < canvas.width; px += pxprecision) {
|
||||
for(let px = pxprecision; px < canvas.width; px += pxprecision) {
|
||||
let currentX = canvas.px2x(px)
|
||||
if(!definitionDomain.includes(previousX) && definitionDomain.includes(currentX)) {
|
||||
// Should draw up to currentX, but NOT at previousX.
|
||||
|
@ -171,7 +169,7 @@ export default class Function extends ExecutableObject {
|
|||
do {
|
||||
tmpPx++
|
||||
previousX = canvas.px2x(tmpPx)
|
||||
} while(!definitionDomain.includes(previousX) && currentX > previousX)
|
||||
} while(!definitionDomain.includes(previousX))
|
||||
// Recaclulate previousY
|
||||
previousY = expr.execute(previousX)
|
||||
} else if(!definitionDomain.includes(currentX)) {
|
||||
|
@ -181,7 +179,7 @@ export default class Function extends ExecutableObject {
|
|||
do {
|
||||
tmpPx--
|
||||
currentX = canvas.px2x(tmpPx)
|
||||
} while(!definitionDomain.includes(currentX) && currentX > previousX)
|
||||
} while(!definitionDomain.includes(currentX) && currentX !== previousX)
|
||||
}
|
||||
// This max variation is needed for functions with asymptotical vertical lines (e.g. 1/x, tan x...)
|
||||
let maxvariation = (canvas.px2y(0) - canvas.px2y(canvas.height))
|
||||
|
|
|
@ -53,11 +53,11 @@ export class BoolSetting extends Setting {
|
|||
}
|
||||
|
||||
value() {
|
||||
return Helper.getSetting(this.nameInConfig)
|
||||
return Helper.getSettingBool(this.nameInConfig)
|
||||
}
|
||||
|
||||
set(value) {
|
||||
Helper.setSetting(this.nameInConfig, value === true)
|
||||
Helper.setSettingBool(this.nameInConfig, value)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,11 +69,11 @@ export class NumberSetting extends Setting {
|
|||
}
|
||||
|
||||
value() {
|
||||
return Helper.getSetting(this.nameInConfig)
|
||||
return Helper.getSettingInt(this.nameInConfig)
|
||||
}
|
||||
|
||||
set(value) {
|
||||
Helper.setSetting(this.nameInConfig, +value)
|
||||
Helper.setSettingInt(this.nameInConfig, value)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,11 +84,11 @@ export class EnumIntSetting extends Setting {
|
|||
}
|
||||
|
||||
value() {
|
||||
return Helper.getSetting(this.nameInConfig)
|
||||
return Helper.getSettingInt(this.nameInConfig)
|
||||
}
|
||||
|
||||
set(value) {
|
||||
Helper.setSetting(this.nameInConfig, +value)
|
||||
Helper.setSettingInt(this.nameInConfig, value)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,6 +131,6 @@ export class StringSetting extends Setting {
|
|||
}
|
||||
|
||||
set(value) {
|
||||
Helper.setSetting(this.nameInConfig, ""+value)
|
||||
Helper.setSetting(this.nameInConfig, value)
|
||||
}
|
||||
}
|
|
@ -28,7 +28,7 @@ const XZOOM = new NumberSetting(
|
|||
|
||||
const YZOOM = new NumberSetting(
|
||||
qsTranslate("Settings", "Y Zoom"),
|
||||
"default_graph.yzoom",
|
||||
"default_graph.xzoom",
|
||||
"yzoom",
|
||||
0.1
|
||||
)
|
||||
|
@ -37,7 +37,7 @@ const XMIN = new NumberSetting(
|
|||
qsTranslate("Settings", "Min X"),
|
||||
"default_graph.xmin",
|
||||
"xmin",
|
||||
() => Helper.getSetting("default_graph.logscalex") ? 1e-100 : -Infinity
|
||||
() => Helper.getSettingBool("default_graph.logscalex") ? 1e-100 : -Infinity
|
||||
)
|
||||
|
||||
const YMAX = new NumberSetting(
|
||||
|
|
|
@ -46,15 +46,8 @@ class EnableLatex extends BoolSetting {
|
|||
}
|
||||
}
|
||||
|
||||
const ENABLE_LATEX_ASYNC = new BoolSetting(
|
||||
qsTranslate("general", "Enable threaded LaTeX renderer (experimental)"),
|
||||
"enable_latex_threaded",
|
||||
"new"
|
||||
)
|
||||
|
||||
export default [
|
||||
CHECK_FOR_UPDATES,
|
||||
RESET_REDO_STACK,
|
||||
new EnableLatex(),
|
||||
ENABLE_LATEX_ASYNC
|
||||
new EnableLatex()
|
||||
]
|
||||
|
|
414
common/src/utils.mjs
Normal file
|
@ -0,0 +1,414 @@
|
|||
/**
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
// Add string methods
|
||||
/**
|
||||
* Replaces latin characters with their uppercase versions.
|
||||
* @return {string}
|
||||
*/
|
||||
String.prototype.toLatinUppercase = String.prototype.toLatinUppercase || function() {
|
||||
return this.replace(/[a-z]/g, function(match) {
|
||||
return match.toUpperCase()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the 'enclosers' of a string (e.g. quotes, parentheses, brackets...)
|
||||
* @return {string}
|
||||
*/
|
||||
String.prototype.removeEnclosure = function() {
|
||||
return this.substring(1, this.length - 1)
|
||||
}
|
||||
|
||||
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'],
|
||||
[/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
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,'&').replace(/</g,'<').replace(/>/g,'>') ;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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(''))
|
||||
}
|
|
@ -1,258 +0,0 @@
|
|||
/**
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
import { textsub, textsup } from "./subsup.mjs"
|
||||
|
||||
/**
|
||||
* Simplifies (mathematically) a mathematical expression.
|
||||
* @deprecated
|
||||
* @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.
|
||||
* @deprecated
|
||||
* @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"],
|
||||
[/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
|
||||
}
|
||||
|
||||
/** @type {[RegExp, string][]} */
|
||||
const replacements = [
|
||||
// Greek letters
|
||||
[/(\W|^)al(pha)?(\W|$)/g, "$1α$3"],
|
||||
[/(\W|^)be(ta)?(\W|$)/g, "$1β$3"],
|
||||
[/(\W|^)ga(mma)?(\W|$)/g, "$1γ$3"],
|
||||
[/(\W|^)de(lta)?(\W|$)/g, "$1δ$3"],
|
||||
[/(\W|^)ep(silon)?(\W|$)/g, "$1ε$3"],
|
||||
[/(\W|^)ze(ta)?(\W|$)/g, "$1ζ$3"],
|
||||
[/(\W|^)et(a)?(\W|$)/g, "$1η$3"],
|
||||
[/(\W|^)th(eta)?(\W|$)/g, "$1θ$3"],
|
||||
[/(\W|^)io(ta)?(\W|$)/g, "$1ι$3"],
|
||||
[/(\W|^)ka(ppa)?(\W|$)/g, "$1κ$3"],
|
||||
[/(\W|^)la(mbda)?(\W|$)/g, "$1λ$3"],
|
||||
[/(\W|^)mu(\W|$)/g, "$1μ$2"],
|
||||
[/(\W|^)nu(\W|$)/g, "$1ν$2"],
|
||||
[/(\W|^)xi(\W|$)/g, "$1ξ$2"],
|
||||
[/(\W|^)rh(o)?(\W|$)/g, "$1ρ$3"],
|
||||
[/(\W|^)si(gma)?(\W|$)/g, "$1σ$3"],
|
||||
[/(\W|^)ta(u)?(\W|$)/g, "$1τ$3"],
|
||||
[/(\W|^)up(silon)?(\W|$)/g, "$1υ$3"],
|
||||
[/(\W|^)ph(i)?(\W|$)/g, "$1φ$3"],
|
||||
[/(\W|^)ch(i)?(\W|$)/g, "$1χ$3"],
|
||||
[/(\W|^)ps(i)?(\W|$)/g, "$1ψ$3"],
|
||||
[/(\W|^)om(ega)?(\W|$)/g, "$1ω$3"],
|
||||
// Capital greek letters
|
||||
[/(\W|^)gga(mma)?(\W|$)/g, "$1Γ$3"],
|
||||
[/(\W|^)gde(lta)?(\W|$)/g, "$1Δ$3"],
|
||||
[/(\W|^)gth(eta)?(\W|$)/g, "$1Θ$3"],
|
||||
[/(\W|^)gla(mbda)?(\W|$)/g, "$1Λ$3"],
|
||||
[/(\W|^)gxi(\W|$)/g, "$1Ξ$2"],
|
||||
[/(\W|^)gpi(\W|$)/g, "$1Π$2"],
|
||||
[/(\W|^)gsi(gma)?(\W|$)/g, "$1Σ$3"],
|
||||
[/(\W|^)gph(i)?(\W|$)/g, "$1Φ$3"],
|
||||
[/(\W|^)gps(i)?(\W|$)/g, "$1Ψ$3"],
|
||||
[/(\W|^)gom(ega)?(\W|$)/g, "$1Ω$3"],
|
||||
// Array elements
|
||||
[/\[([^\]\[]+)\]/g, function(match, p1) {
|
||||
return textsub(p1)
|
||||
}]
|
||||
]
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
for(const replacement of replacements)
|
||||
str = str.replace(replacement[0], replacement[1])
|
||||
if(removeUnallowed)
|
||||
str = str.replace(/[xnπℝℕ\\∪∩\]\[ ()^/÷*×+=\d¹²³⁴⁵⁶⁷⁸⁹⁰-]/g, "")
|
||||
|
||||
return str
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
/**
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
export * from "./prototype.mjs"
|
||||
export * from "./subsup.mjs"
|
||||
export * from "./expression.mjs"
|
||||
export * from "./other.mjs"
|
|
@ -1,41 +0,0 @@
|
|||
/**
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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, "&").replace(/</g, "<").replace(/>/g, ">")
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
/**
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
// Add string methods
|
||||
/**
|
||||
* Replaces latin characters with their uppercase versions.
|
||||
* @return {string}
|
||||
*/
|
||||
String.prototype.toLatinUppercase = function() {
|
||||
return this.replace(/[a-z]/g, function(match) {
|
||||
return match.toUpperCase()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the first and last character of a string
|
||||
* Used to remove enclosing characters like quotes, parentheses, brackets...
|
||||
* @note Does NOT check for their existence ahead of time.
|
||||
* @return {string}
|
||||
*/
|
||||
String.prototype.removeEnclosure = function() {
|
||||
return this.substring(1, this.length - 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Rounds to a certain number of decimal places.
|
||||
* From https://stackoverflow.com/a/48764436
|
||||
*
|
||||
* @param {number} decimalPlaces
|
||||
* @return {number}
|
||||
*/
|
||||
Number.prototype.toDecimalPrecision = function(decimalPlaces = 0) {
|
||||
const p = Math.pow(10, decimalPlaces)
|
||||
const n = (this * p) * (1 + Number.EPSILON)
|
||||
return Math.round(n) / p
|
||||
}
|
|
@ -1,140 +0,0 @@
|
|||
/**
|
||||
* 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 CHARACTER_TO_POWER = new Map([
|
||||
["-", "⁻"],
|
||||
["+", "⁺"],
|
||||
["=", "⁼"],
|
||||
[" ", " "],
|
||||
["(", "⁽"],
|
||||
[")", "⁾"],
|
||||
["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 CHARACTER_TO_INDICE = new Map([
|
||||
["-", "₋"],
|
||||
["+", "₊"],
|
||||
["=", "₌"],
|
||||
["(", "₍"],
|
||||
[")", "₎"],
|
||||
[" ", " "],
|
||||
["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", "ₓ"]
|
||||
])
|
||||
|
||||
const EXPONENTS = [
|
||||
"⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹"
|
||||
]
|
||||
|
||||
const EXPONENTS_REG = new RegExp("([" + EXPONENTS.join("") + "]+)", "g")
|
||||
|
||||
/**
|
||||
* Put a text in sup position
|
||||
* @param {string} text
|
||||
* @return {string}
|
||||
*/
|
||||
export function textsup(text) {
|
||||
let ret = ""
|
||||
text = text.toString()
|
||||
for(let letter of text)
|
||||
ret += CHARACTER_TO_POWER.has(letter) ? CHARACTER_TO_POWER.get(letter) : letter
|
||||
return ret
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a text in sub position
|
||||
* @param {string} text
|
||||
* @return {string}
|
||||
*/
|
||||
export function textsub(text) {
|
||||
let ret = ""
|
||||
text = text.toString()
|
||||
for(let letter of text)
|
||||
ret += CHARACTER_TO_INDICE.has(letter) ? CHARACTER_TO_INDICE.get(letter) : letter
|
||||
return ret
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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(EXPONENTS_REG, (m, exp) => "^" + exp.split("").map((x) => EXPONENTS.indexOf(x)).join(""))
|
||||
}
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
/**
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
import { describe, it } from "mocha"
|
||||
import { expect } from "chai"
|
||||
|
||||
const { spy } = chaiPlugins
|
||||
|
||||
import { BaseEventEmitter, BaseEvent } from "../../src/events.mjs"
|
||||
|
||||
class MockEmitter extends BaseEventEmitter {
|
||||
static emits = ["example1", "example2"]
|
||||
}
|
||||
|
||||
class MockEvent1 extends BaseEvent {
|
||||
constructor() {
|
||||
super("example1")
|
||||
}
|
||||
}
|
||||
|
||||
class MockEvent2 extends BaseEvent {
|
||||
constructor(parameter) {
|
||||
super("example2")
|
||||
this.parameter = parameter
|
||||
}
|
||||
}
|
||||
|
||||
describe("Lib/EventsEmitters", function() {
|
||||
it("sends events with unique and readonly names", function() {
|
||||
const event = new MockEvent1()
|
||||
expect(event.name).to.equal("example1")
|
||||
expect(() => event.name = "not").to.throw()
|
||||
})
|
||||
|
||||
it("forwards events to all of its listeners", function() {
|
||||
const emitter = new MockEmitter()
|
||||
const listener1 = spy()
|
||||
const listener2 = spy()
|
||||
emitter.on("example1", listener1)
|
||||
emitter.on("example1", listener2)
|
||||
emitter.emit(new MockEvent1())
|
||||
expect(listener1).to.have.been.called.once
|
||||
expect(listener2).to.have.been.called.once
|
||||
})
|
||||
|
||||
it("forwards multiple events to a singular listener", function() {
|
||||
const emitter = new MockEmitter()
|
||||
const listener = spy()
|
||||
const mockEvent1 = new MockEvent1()
|
||||
const mockEvent2 = new MockEvent2(3)
|
||||
emitter.on("example1 example2", listener)
|
||||
emitter.emit(mockEvent1)
|
||||
emitter.emit(mockEvent2)
|
||||
expect(listener).to.have.been.called.twice
|
||||
expect(listener).to.have.been.first.called.with.exactly(mockEvent1)
|
||||
expect(listener).to.have.been.second.called.with.exactly(mockEvent2)
|
||||
})
|
||||
|
||||
it("is able to have listeners removed", function() {
|
||||
const emitter = new MockEmitter()
|
||||
const listener = spy()
|
||||
emitter.on("example1", listener)
|
||||
const removedFromEventItDoesntListenTo = emitter.off("example2", listener)
|
||||
const removedFromEventItListensTo = emitter.off("example1", listener)
|
||||
const removedFromEventASecondTime = emitter.off("example1", listener)
|
||||
expect(removedFromEventItDoesntListenTo).to.be.false
|
||||
expect(removedFromEventItListensTo).to.be.true
|
||||
expect(removedFromEventASecondTime).to.be.false
|
||||
emitter.on("example1 example2", listener)
|
||||
const removedFromBothEvents = emitter.off("example1 example2", listener)
|
||||
expect(removedFromBothEvents).to.be.true
|
||||
emitter.on("example1", listener)
|
||||
const removedFromOneOfTheEvents = emitter.off("example1 example2", listener)
|
||||
expect(removedFromOneOfTheEvents).to.be.true
|
||||
})
|
||||
|
||||
it("is able to have one listener's listening to a single event removed when said listener listens to multiple", function() {
|
||||
const emitter = new MockEmitter()
|
||||
const listener = spy()
|
||||
const mockEvent1 = new MockEvent1()
|
||||
const mockEvent2 = new MockEvent2(3)
|
||||
emitter.on("example1 example2", listener)
|
||||
// Disable listener for example1
|
||||
emitter.off("example1", listener)
|
||||
emitter.emit(mockEvent1)
|
||||
emitter.emit(mockEvent2)
|
||||
expect(listener).to.have.been.called.once
|
||||
expect(listener).to.also.have.been.called.with.exactly(mockEvent2)
|
||||
})
|
||||
|
||||
it("is not able to emit or add/remove listeners for inexistant events", function() {
|
||||
const emitter = new MockEmitter()
|
||||
const listener = spy()
|
||||
expect(() => emitter.on("inexistant", listener)).to.throw(Error)
|
||||
expect(() => emitter.off("inexistant", listener)).to.throw(Error)
|
||||
expect(() => emitter.emit(new BaseEvent("inexistant"))).to.throw(Error)
|
||||
})
|
||||
|
||||
it("isn't able to emit non-events", function() {
|
||||
const emitter = new MockEmitter()
|
||||
expect(() => emitter.emit("not-an-event")).to.throw(Error)
|
||||
})
|
||||
})
|
|
@ -1,58 +0,0 @@
|
|||
/**
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
import { describe, it } from "mocha"
|
||||
import { expect } from "chai"
|
||||
|
||||
import { MockLatex } from "../mock/latex.mjs"
|
||||
import { MockHelper } from "../mock/helper.mjs"
|
||||
import {
|
||||
CanvasInterface,
|
||||
DialogInterface,
|
||||
HelperInterface,
|
||||
Interface,
|
||||
LatexInterface,
|
||||
RootInterface
|
||||
} from "../../src/module/interface.mjs"
|
||||
import { MockDialog } from "../mock/dialog.mjs"
|
||||
import { MockRootElement } from "../mock/root.mjs"
|
||||
import { MockCanvas } from "../mock/canvas.mjs"
|
||||
|
||||
describe("Interfaces", function() {
|
||||
describe("#interface methods", function() {
|
||||
it("throws an error when called directly", function() {
|
||||
const obj = new CanvasInterface()
|
||||
expect(() => obj.markDirty()).to.throw(Error)
|
||||
})
|
||||
})
|
||||
|
||||
describe("#checkImplementation", function() {
|
||||
it("validates the implementation of mocks", function() {
|
||||
const checkMockLatex = () => Interface.checkImplementation(LatexInterface, new MockLatex())
|
||||
const checkMockHelper = () => Interface.checkImplementation(HelperInterface, new MockHelper())
|
||||
const checkMockDialog = () => Interface.checkImplementation(DialogInterface, new MockDialog())
|
||||
const checkMockRoot = () => Interface.checkImplementation(RootInterface, new MockRootElement())
|
||||
const checkMockCanvas = () => Interface.checkImplementation(CanvasInterface, new MockCanvas())
|
||||
expect(checkMockLatex).to.not.throw()
|
||||
expect(checkMockHelper).to.not.throw()
|
||||
expect(checkMockDialog).to.not.throw()
|
||||
expect(checkMockRoot).to.not.throw()
|
||||
expect(checkMockCanvas).to.not.throw()
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,232 +0,0 @@
|
|||
/**
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
// Load prior tests
|
||||
|
||||
import { describe, it } from "mocha"
|
||||
import { expect } from "chai"
|
||||
|
||||
import * as Polyfill from "../../src/lib/expr-eval/polyfill.mjs"
|
||||
import {
|
||||
andOperator,
|
||||
cbrt,
|
||||
equal,
|
||||
expm1,
|
||||
hypot,
|
||||
lessThan,
|
||||
log1p,
|
||||
log2,
|
||||
notEqual
|
||||
} from "../../src/lib/expr-eval/polyfill.mjs"
|
||||
|
||||
describe("Math/Polyfill", () => {
|
||||
describe("#add", function() {
|
||||
it("adds two numbers", function() {
|
||||
expect(Polyfill.add(2, 3)).to.equal(5)
|
||||
expect(Polyfill.add("2", "3")).to.equal(5)
|
||||
})
|
||||
})
|
||||
|
||||
describe("#sub", function() {
|
||||
it("subtracts two numbers", function() {
|
||||
expect(Polyfill.sub(2, 1)).to.equal(1)
|
||||
expect(Polyfill.sub("2", "1")).to.equal(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("#mul", function() {
|
||||
it("multiplies two numbers", function() {
|
||||
expect(Polyfill.mul(2, 3)).to.equal(6)
|
||||
expect(Polyfill.mul("2", "3")).to.equal(6)
|
||||
})
|
||||
})
|
||||
|
||||
describe("#div", function() {
|
||||
it("divides two numbers", function() {
|
||||
expect(Polyfill.div(10, 2)).to.equal(5)
|
||||
expect(Polyfill.div("10", "2")).to.equal(5)
|
||||
})
|
||||
})
|
||||
|
||||
describe("#mod", function() {
|
||||
it("returns the modulo of two numbers", function() {
|
||||
expect(Polyfill.mod(10, 3)).to.equal(1)
|
||||
expect(Polyfill.mod("10", "3")).to.equal(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe("#concat", function() {
|
||||
it("returns the concatenation of two strings", function() {
|
||||
expect(Polyfill.concat(10, 3)).to.equal("103")
|
||||
expect(Polyfill.concat("abc", "def")).to.equal("abcdef")
|
||||
})
|
||||
})
|
||||
|
||||
describe("#equal", function() {
|
||||
it("returns whether its two arguments are equal", function() {
|
||||
expect(Polyfill.equal(10, 3)).to.be.false
|
||||
expect(Polyfill.equal(10, 10)).to.be.true
|
||||
expect(Polyfill.equal("abc", "def")).to.be.false
|
||||
expect(Polyfill.equal("abc", "abc")).to.be.true
|
||||
})
|
||||
})
|
||||
|
||||
describe("#notEqual", function() {
|
||||
it("returns whether its two arguments are not equal", function() {
|
||||
expect(Polyfill.notEqual(10, 3)).to.be.true
|
||||
expect(Polyfill.notEqual(10, 10)).to.be.false
|
||||
expect(Polyfill.notEqual("abc", "def")).to.be.true
|
||||
expect(Polyfill.notEqual("abc", "abc")).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
describe("#greaterThan", function() {
|
||||
it("returns whether its first argument is strictly greater than its second", function() {
|
||||
expect(Polyfill.greaterThan(10, 3)).to.be.true
|
||||
expect(Polyfill.greaterThan(10, 10)).to.be.false
|
||||
expect(Polyfill.greaterThan(10, 30)).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
describe("#lessThan", function() {
|
||||
it("returns whether its first argument is strictly less than its second", function() {
|
||||
expect(Polyfill.lessThan(10, 3)).to.be.false
|
||||
expect(Polyfill.lessThan(10, 10)).to.be.false
|
||||
expect(Polyfill.lessThan(10, 30)).to.be.true
|
||||
})
|
||||
})
|
||||
|
||||
describe("#greaterThanEqual", function() {
|
||||
it("returns whether its first argument is greater or equal to its second", function() {
|
||||
expect(Polyfill.greaterThanEqual(10, 3)).to.be.true
|
||||
expect(Polyfill.greaterThanEqual(10, 10)).to.be.true
|
||||
expect(Polyfill.greaterThanEqual(10, 30)).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
describe("#lessThanEqual", function() {
|
||||
it("returns whether its first argument is strictly less than its second", function() {
|
||||
expect(Polyfill.lessThanEqual(10, 3)).to.be.false
|
||||
expect(Polyfill.lessThanEqual(10, 10)).to.be.true
|
||||
expect(Polyfill.lessThanEqual(10, 30)).to.be.true
|
||||
})
|
||||
})
|
||||
|
||||
describe("#andOperator", function() {
|
||||
it("returns whether its arguments are both true", function() {
|
||||
expect(Polyfill.andOperator(true, true)).to.be.true
|
||||
expect(Polyfill.andOperator(true, false)).to.be.false
|
||||
expect(Polyfill.andOperator(false, true)).to.be.false
|
||||
expect(Polyfill.andOperator(false, false)).to.be.false
|
||||
expect(Polyfill.andOperator(10, 3)).to.be.true
|
||||
expect(Polyfill.andOperator(10, 0)).to.be.false
|
||||
expect(Polyfill.andOperator(0, 0)).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
describe("#orOperator", function() {
|
||||
it("returns whether one of its arguments is true", function() {
|
||||
expect(Polyfill.orOperator(true, true)).to.be.true
|
||||
expect(Polyfill.orOperator(true, false)).to.be.true
|
||||
expect(Polyfill.orOperator(false, true)).to.be.true
|
||||
expect(Polyfill.orOperator(false, false)).to.be.false
|
||||
expect(Polyfill.orOperator(10, 3)).to.be.true
|
||||
expect(Polyfill.orOperator(10, 0)).to.be.true
|
||||
expect(Polyfill.orOperator(0, 0)).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
describe("#inOperator", function() {
|
||||
it("checks if second argument contains first", function() {
|
||||
expect(Polyfill.inOperator("a", ["a", "b", "c"])).to.be.true
|
||||
expect(Polyfill.inOperator(3, [0, 1, 2])).to.be.false
|
||||
expect(Polyfill.inOperator(3, [0, 1, 3, 2])).to.be.true
|
||||
expect(Polyfill.inOperator("a", "abcdef")).to.be.true
|
||||
expect(Polyfill.inOperator("a", "bcdefg")).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
describe("#sinh, #cosh, #tanh, #asinh, #acosh, #atanh", function() {
|
||||
const EPSILON = 1e-12
|
||||
for(let x = -.9; x < 1; x += 0.1) {
|
||||
expect(Polyfill.sinh(x)).to.be.approximately(Math.sinh(x), EPSILON)
|
||||
expect(Polyfill.cosh(x)).to.be.approximately(Math.cosh(x), EPSILON)
|
||||
expect(Polyfill.tanh(x)).to.be.approximately(Math.tanh(x), EPSILON)
|
||||
expect(Polyfill.asinh(x)).to.be.approximately(Math.asinh(x), EPSILON)
|
||||
expect(Polyfill.atanh(x)).to.be.approximately(Math.atanh(x), EPSILON)
|
||||
}
|
||||
for(let x = 1.1; x < 10; x += 0.1) {
|
||||
expect(Polyfill.sinh(x)).to.be.approximately(Math.sinh(x), EPSILON)
|
||||
expect(Polyfill.cosh(x)).to.be.approximately(Math.cosh(x), EPSILON)
|
||||
expect(Polyfill.tanh(x)).to.be.approximately(Math.tanh(x), EPSILON)
|
||||
expect(Polyfill.asinh(x)).to.be.approximately(Math.asinh(x), EPSILON)
|
||||
expect(Polyfill.acosh(x)).to.be.approximately(Math.acosh(x), EPSILON)
|
||||
expect(Polyfill.log10(x)).to.be.approximately(Math.log10(x), EPSILON)
|
||||
}
|
||||
})
|
||||
|
||||
describe("#trunc", function() {
|
||||
it("returns the decimal part of floats", function() {
|
||||
for(let x = -10; x < 10; x += 0.1)
|
||||
expect(Polyfill.trunc(x)).to.equal(Math.trunc(x))
|
||||
})
|
||||
})
|
||||
|
||||
describe("#gamma", function() {
|
||||
it("returns the product of factorial(x - 1)", function() {
|
||||
expect(Polyfill.gamma(0)).to.equal(Infinity)
|
||||
expect(Polyfill.gamma(1)).to.equal(1)
|
||||
expect(Polyfill.gamma(2)).to.equal(1)
|
||||
expect(Polyfill.gamma(3)).to.equal(2)
|
||||
expect(Polyfill.gamma(4)).to.equal(6)
|
||||
expect(Polyfill.gamma(5)).to.equal(24)
|
||||
expect(Polyfill.gamma(172)).to.equal(Infinity)
|
||||
expect(Polyfill.gamma(172.3)).to.equal(Infinity)
|
||||
expect(Polyfill.gamma(.2)).to.approximately(4.590_843_712, 1e-8)
|
||||
expect(Polyfill.gamma(5.5)).to.be.approximately(52.34277778, 1e-8)
|
||||
expect(Polyfill.gamma(90.2)).to.equal(4.0565358202825355e+136)
|
||||
})
|
||||
})
|
||||
|
||||
describe("#hypot", function() {
|
||||
it("returns the hypothenus length of a triangle whose length are provided in arguments", function() {
|
||||
for(let x = 0; x < 10; x += 0.3) {
|
||||
expect(Polyfill.hypot(x)).to.be.approximately(Math.hypot(x), Number.EPSILON)
|
||||
for(let y = 0; y < 10; y += 0.3) {
|
||||
expect(Polyfill.hypot(x, y)).to.be.approximately(Math.hypot(x, y), Number.EPSILON)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("#sign, #cbrt, #exmp1", function() {
|
||||
for(let x = -10; x < 10; x += 0.3) {
|
||||
expect(Polyfill.sign(x)).to.approximately(Math.sign(x), 1e-12)
|
||||
expect(Polyfill.cbrt(x)).to.approximately(Math.cbrt(x), 1e-12)
|
||||
expect(Polyfill.expm1(x)).to.approximately(Math.expm1(x), 1e-12)
|
||||
}
|
||||
})
|
||||
|
||||
describe("#log1p, #log2", function() {
|
||||
for(let x = 1; x < 10; x += 0.3) {
|
||||
expect(Polyfill.log1p(x)).to
|
||||
.be.approximately(Math.log1p(x), 1e-12)
|
||||
expect(Polyfill.log2(x)).to.be.approximately(Math.log2(x), 1e-12)
|
||||
}
|
||||
})
|
||||
})
|
|
@ -1,183 +0,0 @@
|
|||
/**
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
import { textsup, textsub, parseName, getRandomColor, escapeHTML, exponentsToExpression } from "../../src/utils/index.mjs"
|
||||
|
||||
|
||||
import { describe, it } from "mocha"
|
||||
import { expect } from "chai"
|
||||
|
||||
describe("Lib/PrototypeExtensions", function() {
|
||||
describe("#String.toLatinUppercase", function() {
|
||||
it("transforms latin characters from strings to their uppercase version", function() {
|
||||
expect("abc".toLatinUppercase()).to.equal("ABC")
|
||||
expect("abCd".toLatinUppercase()).to.equal("ABCD")
|
||||
expect("ab123cd456".toLatinUppercase()).to.equal("AB123CD456")
|
||||
expect("ABC".toLatinUppercase()).to.equal("ABC")
|
||||
})
|
||||
|
||||
it("does not transform non latin characters to their uppercase version", function() {
|
||||
expect("abαπ".toLatinUppercase()).to.equal("ABαπ")
|
||||
expect("abαπ".toLatinUppercase()).to.not.equal("abαπ".toUpperCase())
|
||||
})
|
||||
})
|
||||
|
||||
describe("#String.removeEnclosure", function() {
|
||||
it("is able to remove the first and last characters", function() {
|
||||
expect("[1+t]".removeEnclosure()).to.equal("1+t")
|
||||
expect('"a+b+c*d"'.removeEnclosure()).to.equal("a+b+c*d")
|
||||
expect("(pi/2)".removeEnclosure()).to.equal("pi/2")
|
||||
})
|
||||
})
|
||||
|
||||
describe("#Number.toDecimalPrecision", function() {
|
||||
it("rounds a number to a fixed decimal precision", function() {
|
||||
expect(123.456789.toDecimalPrecision()).to.equal(123)
|
||||
expect(123.456789.toDecimalPrecision(1)).to.equal(123.5)
|
||||
expect(123.456789.toDecimalPrecision(2)).to.equal(123.46)
|
||||
expect(123.456789.toDecimalPrecision(3)).to.equal(123.457)
|
||||
expect(123.456789.toDecimalPrecision(4)).to.equal(123.4568)
|
||||
expect(123.456789.toDecimalPrecision(5)).to.equal(123.45679)
|
||||
expect(123.456789.toDecimalPrecision(6)).to.equal(123.456789)
|
||||
expect(123.111111.toDecimalPrecision(5)).to.equal(123.11111)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("Lib/Utils", function() {
|
||||
describe("#textsup", function() {
|
||||
it("transforms characters which have a sup unicode equivalent", function() {
|
||||
expect(textsup("-+=()")).to.equal("⁻⁺⁼⁽⁾")
|
||||
expect(textsup("0123456789")).to.equal("⁰¹²³⁴⁵⁶⁷⁸⁹")
|
||||
expect(textsup("abcdefghijklmnoprstuvwxyz")).to.equal("ᵃᵇᶜᵈᵉᶠᵍʰⁱʲᵏˡᵐⁿᵒᵖʳˢᵗᵘᵛʷˣʸᶻ")
|
||||
})
|
||||
|
||||
it("does not transform characters without a sup equivalent", function() {
|
||||
expect(textsup("ABCDEFGHIJKLMNOPQRSTUVWXYZq")).to.equal("ABCDEFGHIJKLMNOPQRSTUVWXYZq")
|
||||
})
|
||||
|
||||
it("partially transforms strings which only have a few characters with a sup equivalent", function() {
|
||||
expect(textsup("ABCabcABC")).to.equal("ABCᵃᵇᶜABC")
|
||||
})
|
||||
})
|
||||
|
||||
describe("#textsub", function() {
|
||||
it("transforms characters which have a sub unicode equivalent", function() {
|
||||
expect(textsub("-+=()")).to.equal("₋₊₌₍₎")
|
||||
expect(textsub("0123456789")).to.equal("₀₁₂₃₄₅₆₇₈₉")
|
||||
expect(textsub("aehijklmnoprstuvx")).to.equal("ₐₑₕᵢⱼₖₗₘₙₒₚᵣₛₜᵤᵥₓ")
|
||||
})
|
||||
|
||||
it("does not transform characters without a sub equivalent", function() {
|
||||
expect(textsub("ABCDEFGHIJKLMNOPQRSTUVWXYZ")).to.equal("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
expect(textsub("bcdfgqyz")).to.equal("bcdfgqyz")
|
||||
})
|
||||
|
||||
it("partially transforms strings which only have a few characters with a sub equivalent", function() {
|
||||
expect(textsub("ABC123ABC")).to.equal("ABC₁₂₃ABC")
|
||||
})
|
||||
})
|
||||
|
||||
describe("#parseName", function() {
|
||||
it("parses greek letter names", function() {
|
||||
const shorthands = {
|
||||
"α": ["al", "alpha"],
|
||||
"β": ["be", "beta"],
|
||||
"γ": ["ga", "gamma"],
|
||||
"δ": ["de", "delta"],
|
||||
"ε": ["ep", "epsilon"],
|
||||
"ζ": ["ze", "zeta"],
|
||||
"η": ["et", "eta"],
|
||||
"θ": ["th", "theta"],
|
||||
"κ": ["ka", "kappa"],
|
||||
"λ": ["la", "lambda"],
|
||||
"μ": ["mu"],
|
||||
"ν": ["nu"],
|
||||
"ξ": ["xi"],
|
||||
"ρ": ["rh", "rho"],
|
||||
"σ": ["si", "sigma"],
|
||||
"τ": ["ta", "tau"],
|
||||
"υ": ["up", "upsilon"],
|
||||
"φ": ["ph", "phi"],
|
||||
"χ": ["ch", "chi"],
|
||||
"ψ": ["ps", "psi"],
|
||||
"ω": ["om", "omega"],
|
||||
"Γ": ["gga", "ggamma"],
|
||||
"Δ": ["gde", "gdelta"],
|
||||
"Θ": ["gth", "gtheta"],
|
||||
"Λ": ["gla", "glambda"],
|
||||
"Ξ": ["gxi"],
|
||||
"Π": ["gpi"],
|
||||
"Σ": ["gsi", "gsigma"],
|
||||
"Φ": ["gph", "gphi"],
|
||||
"Ψ": ["gps", "gpsi"],
|
||||
"Ω": ["gom", "gomega"],
|
||||
}
|
||||
for(const [char, shorts] of Object.entries(shorthands)) {
|
||||
expect(parseName(char)).to.equal(char)
|
||||
for(const short of shorts)
|
||||
expect(parseName(short)).to.equal(char)
|
||||
}
|
||||
})
|
||||
|
||||
it("parses array elements into sub", function() {
|
||||
expect(parseName("u[n+1]")).to.equal("uₙ₊₁")
|
||||
expect(parseName("u[(n+x)]")).to.equal("u₍ₙ₊ₓ₎")
|
||||
expect(parseName("u[A]")).to.equal("uA")
|
||||
})
|
||||
|
||||
it("removes disallowed characters when indicated", function() {
|
||||
const disallowed = "xnπℝℕ\\∪∩[] ()^/^/÷*×+=1234567890¹²³⁴⁵⁶⁷⁸⁹⁰-"
|
||||
expect(parseName(disallowed)).to.equal("")
|
||||
expect(parseName("AA" + disallowed)).to.equal("AA")
|
||||
expect(parseName(disallowed, false)).to.equal(disallowed)
|
||||
})
|
||||
|
||||
it("is able to do all three at once", function() {
|
||||
expect(parseName("al[n+1]+n")).to.equal("αₙ₊₁")
|
||||
expect(parseName("al[n+1]+n", false)).to.equal("αₙ₊₁+n")
|
||||
})
|
||||
})
|
||||
|
||||
describe("#getRandomColor", function() {
|
||||
it("provides a valid color", function() {
|
||||
const colorReg = /^#[A-F\d]{6}$/
|
||||
for(let i = 0; i < 50; i++)
|
||||
expect(getRandomColor()).to.match(colorReg)
|
||||
})
|
||||
})
|
||||
|
||||
describe("#escapeHTML", function() {
|
||||
it("escapes ampersands", function() {
|
||||
expect(escapeHTML("&")).to.equal("&")
|
||||
expect(escapeHTML("High & Mighty")).to.equal("High & Mighty")
|
||||
})
|
||||
|
||||
it("escapes injected HTML tags", function() {
|
||||
expect(escapeHTML("<script>alert('Injected!')</script>")).to.equal("<script>alert('Injected!')</script>")
|
||||
expect(escapeHTML('<a href="javascript:alert()">Link</a>')).to.equal('<a href="javascript:alert()">Link</a>')
|
||||
})
|
||||
})
|
||||
|
||||
describe("#exponentsToExpression", function() {
|
||||
it("transforms exponents to power expression", function() {
|
||||
expect(exponentsToExpression("x¹²³⁴⁵⁶⁷⁸⁹⁰")).to.equal("x^1234567890")
|
||||
expect(exponentsToExpression("x¹²+y³⁴")).to.equal("x^12+y^34")
|
||||
})
|
||||
})
|
||||
})
|
|
@ -15,22 +15,16 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import * as fs from "./mock/fs.mjs"
|
||||
import Qt from "./mock/qt.mjs"
|
||||
import { MockHelper } from "./mock/helper.mjs"
|
||||
import { MockLatex } from "./mock/latex.mjs"
|
||||
|
||||
import { use } from "chai"
|
||||
import spies from "chai-spies"
|
||||
import promised from "chai-as-promised"
|
||||
import * as fs from "./mock/fs.mjs";
|
||||
import Qt from "./mock/qt.mjs";
|
||||
import { MockHelper } from "./mock/helper.mjs";
|
||||
import { MockLatex } from "./mock/latex.mjs";
|
||||
import Modules from "../src/module/index.mjs";
|
||||
|
||||
function setup() {
|
||||
use(promised)
|
||||
const { spy } = use(spies)
|
||||
|
||||
globalThis.Helper = new MockHelper()
|
||||
globalThis.Latex = new MockLatex()
|
||||
globalThis.chaiPlugins = { spy }
|
||||
Modules.Latex.initialize({ latex: Latex, helper: Helper })
|
||||
}
|
||||
|
||||
setup()
|
||||
|
|
|
@ -19,46 +19,46 @@
|
|||
import { describe, it } from "mocha"
|
||||
import { expect } from "chai"
|
||||
|
||||
// import { Domain, parseDomainSimple } from "../../src/math/domain.mjs"
|
||||
//
|
||||
// describe("math.domain", function() {
|
||||
// describe("#parseDomainSimple", function() {
|
||||
// it("returns predefined domains", function() {
|
||||
// const predefinedToCheck = [
|
||||
// // Real domains
|
||||
// { domain: Domain.R, shortcuts: ["R", "ℝ"] },
|
||||
// // Zero exclusive real domains
|
||||
// { domain: Domain.RE, shortcuts: ["RE", "R*", "ℝ*"] },
|
||||
// // Real positive domains
|
||||
// { domain: Domain.RP, shortcuts: ["RP", "R+", "ℝ⁺", "ℝ+"] },
|
||||
// // Zero-exclusive real positive domains
|
||||
// { domain: Domain.RPE, shortcuts: ["RPE", "REP", "R+*", "R*+", "ℝ*⁺", "ℝ⁺*", "ℝ*+", "ℝ+*"] },
|
||||
// // Real negative domain
|
||||
// { domain: Domain.RM, shortcuts: ["RM", "R-", "ℝ⁻", "ℝ-"] },
|
||||
// // Zero-exclusive real negative domains
|
||||
// { domain: Domain.RME, shortcuts: ["RME", "REM", "R-*", "R*-", "ℝ⁻*", "ℝ*⁻", "ℝ-*", "ℝ*-"] },
|
||||
// // Natural integers domain
|
||||
// { domain: Domain.N, shortcuts: ["ℕ", "N", "ZP", "Z+", "ℤ⁺", "ℤ+"] },
|
||||
// // Zero-exclusive natural integers domain
|
||||
// { domain: Domain.NE, shortcuts: ["NE", "NP", "N*", "N+", "ℕ*", "ℕ⁺", "ℕ+", "ZPE", "ZEP", "Z+*", "Z*+", "ℤ⁺*", "ℤ*⁺", "ℤ+*", "ℤ*+"] },
|
||||
// // Logarithmic natural domains
|
||||
// { domain: Domain.NLog, shortcuts: ["NLOG", "ℕˡᵒᵍ", "ℕLOG"] },
|
||||
// // All integers domains
|
||||
// { domain: Domain.Z, shortcuts: ["Z", "ℤ"] },
|
||||
// // Zero-exclusive all integers domain
|
||||
// { domain: Domain.ZE, shortcuts: ["ZE", "Z*", "ℤ*"] },
|
||||
// // Negative integers domain
|
||||
// { domain: Domain.ZM, shortcuts: ["ZM", "Z-", "ℤ⁻", "ℤ-"] },
|
||||
// // Zero-exclusive negative integers domain
|
||||
// { domain: Domain.ZME, shortcuts: ["ZME", "ZEM", "Z-*", "Z*-", "ℤ⁻*", "ℤ*⁻", "ℤ-*", "ℤ*-"] },
|
||||
// ]
|
||||
//
|
||||
// // Real domains
|
||||
// for(const { domain, shortcuts } of predefinedToCheck)
|
||||
// for(const shortcut of shortcuts)
|
||||
// expect(parseDomainSimple(shortcut)).to.be.equal(domain)
|
||||
// })
|
||||
//
|
||||
// it("")
|
||||
// })
|
||||
// })
|
||||
import { Domain, parseDomainSimple } from "../../src/math/domain.mjs"
|
||||
|
||||
describe("math.domain", function() {
|
||||
describe("#parseDomainSimple", function() {
|
||||
it("returns predefined domains", function() {
|
||||
const predefinedToCheck = [
|
||||
// Real domains
|
||||
{ domain: Domain.R, shortcuts: ["R", "ℝ"] },
|
||||
// Zero exclusive real domains
|
||||
{ domain: Domain.RE, shortcuts: ["RE", "R*", "ℝ*"] },
|
||||
// Real positive domains
|
||||
{ domain: Domain.RP, shortcuts: ["RP", "R+", "ℝ⁺", "ℝ+"] },
|
||||
// Zero-exclusive real positive domains
|
||||
{ domain: Domain.RPE, shortcuts: ["RPE", "REP", "R+*", "R*+", "ℝ*⁺", "ℝ⁺*", "ℝ*+", "ℝ+*"] },
|
||||
// Real negative domain
|
||||
{ domain: Domain.RM, shortcuts: ["RM", "R-", "ℝ⁻", "ℝ-"] },
|
||||
// Zero-exclusive real negative domains
|
||||
{ domain: Domain.RME, shortcuts: ["RME", "REM", "R-*", "R*-", "ℝ⁻*", "ℝ*⁻", "ℝ-*", "ℝ*-"] },
|
||||
// Natural integers domain
|
||||
{ domain: Domain.N, shortcuts: ["ℕ", "N", "ZP", "Z+", "ℤ⁺", "ℤ+"] },
|
||||
// Zero-exclusive natural integers domain
|
||||
{ domain: Domain.NE, shortcuts: ["NE", "NP", "N*", "N+", "ℕ*", "ℕ⁺", "ℕ+", "ZPE", "ZEP", "Z+*", "Z*+", "ℤ⁺*", "ℤ*⁺", "ℤ+*", "ℤ*+"] },
|
||||
// Logarithmic natural domains
|
||||
{ domain: Domain.NLog, shortcuts: ["NLOG", "ℕˡᵒᵍ", "ℕLOG"] },
|
||||
// All integers domains
|
||||
{ domain: Domain.Z, shortcuts: ["Z", "ℤ"] },
|
||||
// Zero-exclusive all integers domain
|
||||
{ domain: Domain.ZE, shortcuts: ["ZE", "Z*", "ℤ*"] },
|
||||
// Negative integers domain
|
||||
{ domain: Domain.ZM, shortcuts: ["ZM", "Z-", "ℤ⁻", "ℤ-"] },
|
||||
// Zero-exclusive negative integers domain
|
||||
{ domain: Domain.ZME, shortcuts: ["ZME", "ZEM", "Z-*", "Z*-", "ℤ⁻*", "ℤ*⁻", "ℤ-*", "ℤ*-"] },
|
||||
]
|
||||
|
||||
// Real domains
|
||||
for(const { domain, shortcuts } of predefinedToCheck)
|
||||
for(const shortcut of shortcuts)
|
||||
expect(parseDomainSimple(shortcut)).to.be.equal(domain)
|
||||
})
|
||||
|
||||
it("")
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,181 +0,0 @@
|
|||
/**
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
// Load prior tests
|
||||
import "../basics/utils.mjs"
|
||||
import "../module/latex.mjs"
|
||||
import "../module/expreval.mjs"
|
||||
import "../module/objects.mjs"
|
||||
|
||||
import { describe, it } from "mocha"
|
||||
import { expect } from "chai"
|
||||
|
||||
import { executeExpression, Expression } from "../../src/math/expression.mjs"
|
||||
import ExprEval from "../../src/module/expreval.mjs"
|
||||
|
||||
|
||||
describe("Math/Expression", function() {
|
||||
describe("#constructor", function() {
|
||||
it("accepts strings", function() {
|
||||
expect(() => new Expression("2+3")).to.not.throw
|
||||
expect(() => new Expression("x+2")).to.not.throw
|
||||
})
|
||||
|
||||
it("accepts already parsed expressions", function() {
|
||||
expect(() => new Expression(ExprEval.parse("2+3"))).to.not.throw
|
||||
expect(() => new Expression(ExprEval.parse("x+2"))).to.not.throw
|
||||
})
|
||||
|
||||
it("doesn't accept anything else", function() {
|
||||
expect(() => new Expression()).to.throw("Cannot create an expression with undefined.")
|
||||
expect(() => new Expression(12)).to.throw("Cannot create an expression with a Number.")
|
||||
expect(() => new Expression({})).to.throw("Cannot create an expression with a Object.")
|
||||
expect(() => new Expression(true)).to.throw("Cannot create an expression with a Boolean.")
|
||||
})
|
||||
})
|
||||
|
||||
describe("#variables", function() {
|
||||
it("returns a list of variables for non-constant expressions", function() {
|
||||
expect(new Expression("x+1").variables()).to.deep.equal(["x"])
|
||||
expect(new Expression("x+n").variables()).to.deep.equal(["x", "n"])
|
||||
expect(new Expression("u[n] + A.x").variables()).to.deep.equal(["u", "n", "A"])
|
||||
})
|
||||
|
||||
it("returns an empty array if the expression is constant", function() {
|
||||
expect(new Expression("2+1").variables()).to.deep.equal([])
|
||||
expect(new Expression("sin π").variables()).to.deep.equal([])
|
||||
expect(new Expression("e^3").variables()).to.deep.equal([])
|
||||
})
|
||||
})
|
||||
|
||||
describe("#isConstant", function() {
|
||||
it("returns true if neither x nor n are included into the expression", function() {
|
||||
expect(new Expression("2+1").isConstant()).to.be.true
|
||||
expect(new Expression("e^3").isConstant()).to.be.true
|
||||
expect(new Expression("2+f(3)").isConstant()).to.be.true
|
||||
expect(new Expression("sin A.x").isConstant()).to.be.true
|
||||
})
|
||||
|
||||
it("returns false if either x or n are included into the expression", function() {
|
||||
expect(new Expression("2+x").isConstant()).to.be.false
|
||||
expect(new Expression("e^n").isConstant()).to.be.false
|
||||
expect(new Expression("2+f(x)").isConstant()).to.be.false
|
||||
expect(new Expression("n + sin x").isConstant()).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
describe("#requiredObjects", function() {
|
||||
it("returns the list of objects that need to be registered for this expression", function() {
|
||||
expect(new Expression("x^n").requiredObjects()).to.deep.equal([])
|
||||
expect(new Expression("2+f(3)").requiredObjects()).to.deep.equal(["f"])
|
||||
expect(new Expression("A.x+x").requiredObjects()).to.deep.equal(["A"])
|
||||
expect(new Expression("2+f(sin A.x)+n").requiredObjects()).to.deep.equal(["f", "A"])
|
||||
})
|
||||
})
|
||||
|
||||
describe.skip("#allRequirementsFulfilled", function() {
|
||||
// TODO: Make tests for objects
|
||||
})
|
||||
|
||||
describe.skip("#undefinedVariables", function() {
|
||||
// TODO: Make tests for objects
|
||||
})
|
||||
|
||||
describe("#toEditableString", function() {
|
||||
it("should return a readable expression", function() {
|
||||
expect(new Expression("2+1").toEditableString()).to.equal("3")
|
||||
expect(new Expression("2+x").toEditableString()).to.equal("(2 + x)")
|
||||
expect(new Expression("x*2+x/3").toEditableString()).to.equal("((x * 2) + (x / 3))")
|
||||
})
|
||||
|
||||
it("should be able to be reparsed and equal the same expression", function() {
|
||||
const exprs = ["5", "x/2", "4/2", "sin x"]
|
||||
for(const expr of exprs) {
|
||||
const exprObj = new Expression(expr)
|
||||
expect(new Expression(exprObj.toEditableString()).calc).to.deep.equal(exprObj.calc)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("#execute", function() {
|
||||
it("returns the result of the computation of the expression", function() {
|
||||
expect(new Expression("2+3").execute()).to.equal(5)
|
||||
expect(new Expression("2+3").execute(10)).to.equal(5)
|
||||
expect(new Expression("2+x").execute(10)).to.equal(12)
|
||||
expect(new Expression("sin x").execute(Math.PI)).to.be.approximately(0, Number.EPSILON)
|
||||
})
|
||||
|
||||
it("returns the cached value if the expression can be cached", function() {
|
||||
const exprs = ["2+3", "x/2", "4/2", "sin x"]
|
||||
for(const expr of exprs) {
|
||||
const exprObj = new Expression(expr)
|
||||
if(exprObj.canBeCached)
|
||||
expect(exprObj.execute()).to.equal(exprObj.cachedValue)
|
||||
else
|
||||
expect(exprObj.execute()).to.not.equal(exprObj.cachedValue)
|
||||
}
|
||||
})
|
||||
|
||||
it("throws an error if some variables are undefined.", function() {
|
||||
expect(() => new Expression("x+n").execute()).to.throw("Undefined variable n.")
|
||||
expect(() => new Expression("sin A.t").execute()).to.throw("Undefined variable A.")
|
||||
expect(() => new Expression("f(3)").execute()).to.throw("Undefined variable f.")
|
||||
})
|
||||
})
|
||||
|
||||
describe("#simplify", function() {
|
||||
it("returns an expression with just the result when no constant or object are used", function() {
|
||||
expect(new Expression("2+2").simplify(Math.PI/2)).to.deep.equal(new Expression("4"))
|
||||
expect(new Expression("x+3").simplify(5)).to.deep.equal(new Expression("8"))
|
||||
expect(new Expression("sin x").simplify(Math.PI/2)).to.deep.equal(new Expression("1"))
|
||||
expect(new Expression("0*e^x").simplify(Math.PI/2)).to.deep.equal(new Expression("0"))
|
||||
})
|
||||
|
||||
it("returns a simplified version of the expression if constants are used", function() {
|
||||
const original = new Expression("e^x").simplify(2)
|
||||
const to = new Expression("e^2")
|
||||
expect(original.toEditableString()).to.deep.equal(to.toEditableString())
|
||||
})
|
||||
})
|
||||
|
||||
describe("#toString", function() {
|
||||
it("returns a human readable string of the expression", function() {
|
||||
expect(new Expression("-2-3").toString()).to.equal("-5")
|
||||
expect(new Expression("0.2+0.1").toString()).to.equal("0.3")
|
||||
expect(new Expression("sin x").toString()).to.equal("sin x")
|
||||
expect(new Expression("sin π").toString()).to.equal("sin π")
|
||||
})
|
||||
|
||||
it("should add a sign if the option is passed", function() {
|
||||
expect(new Expression("-2-3").toString(true)).to.equal("-5")
|
||||
expect(new Expression("2+3").toString(true)).to.equal("+5")
|
||||
})
|
||||
})
|
||||
|
||||
describe("#executeExpression", function() {
|
||||
it("directly computes the result of the expression with no variable", function() {
|
||||
expect(executeExpression("2+3")).to.equal(5)
|
||||
expect(executeExpression("sin (π/2)")).to.equal(1)
|
||||
expect(executeExpression("e^3")).to.be.approximately(Math.pow(Math.E, 3), Number.EPSILON)
|
||||
})
|
||||
|
||||
it("throws an error if variables are employed", function() {
|
||||
expect(() => executeExpression("x+n")).to.throw("Undefined variable n.")
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,59 +0,0 @@
|
|||
/**
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
|
||||
export class MockCanvas {
|
||||
constructor(mockLoading = false) {
|
||||
this.mockLoading = mockLoading
|
||||
}
|
||||
|
||||
getContext(context) {
|
||||
throw new Error("MockCanvas.getContext not implemented")
|
||||
}
|
||||
|
||||
markDirty(rect) {
|
||||
this.requestPaint()
|
||||
}
|
||||
|
||||
loadImageAsync(image) {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Image loading is instantaneous.
|
||||
* @param {string} image
|
||||
* @return {boolean}
|
||||
*/
|
||||
isImageLoading(image) {
|
||||
return this.mockLoading
|
||||
}
|
||||
|
||||
/**
|
||||
* Image loading is instantaneous.
|
||||
* @param {string} image
|
||||
* @return {boolean}
|
||||
*/
|
||||
isImageLoaded(image) {
|
||||
return !this.mockLoading
|
||||
}
|
||||
|
||||
requestPaint() {
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
/**
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
export class MockDialog {
|
||||
constructor() {}
|
||||
|
||||
show() {}
|
||||
}
|