Compare commits

..

No commits in common. "master" and "rollup-js" have entirely different histories.

218 changed files with 5478 additions and 11822 deletions

6
.gitignore vendored
View file

@ -37,10 +37,8 @@ docs/html
*.lpf *.lpf
*.lgg *.lgg
# Tests
common/coverage/
**/.coverage
# npm # npm
common/node_modules common/node_modules
common/coverage/
common/.coverage
runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/index.mjs* runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/index.mjs*

View file

@ -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) [![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/) [![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`). - [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 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 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`. `python3 build/runtime-pyside6/LogarithmPlotter/logarithmplotter.py`.
@ -68,19 +68,13 @@ To run LogarithmPlotter's tests, follow these steps:
- Python - Python
- Install python3 and [poetry](https://python-poetry.org/) - Install python3 and [poetry](https://python-poetry.org/)
- Create and activate virtual env (recommended) - Run `poetry install --with test`
- 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 `scripts/run-tests.sh` - Run `scripts/run-tests.sh`
## Legal notice ## Legal notice
LogarithmPlotter - 2D plotter software to make Bode plots, sequences and repartition functions. LogarithmPlotter - 2D plotter software to make Bode plots, sequences and repartition functions.
Copyright (C) 2021-2025 Ad5001 <mail@ad5001.eu> Copyright (C) 2021-2024 Ad5001 <mail@ad5001.eu>
This program is free software: you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
@ -95,8 +89,8 @@ Finally, to actually run the tests:
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. 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 Language files translations located at LogarithmPlotter/i18n are licensed under GNU GPL3.0+ and are copyrighted by their
are copyrighted by their original authors: original authors. See LICENSE.md for more details:
- 🇭🇺 Hungarian translation by [Óvári](https://github.com/ovari) - 🇭🇺 Hungarian translation by [Óvári](https://github.com/ovari)
- 🇳🇴 Norwegian translation by [Allan Nordhøy](https://github.com/comradekingu) - 🇳🇴 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
&lt;r@undefined.ch&gt;, ported to javascript by Matthew Crumley &lt;r@undefined.ch&gt;, ported to javascript by Matthew Crumley
&lt;email@matthewcrumley.com&gt; (http://silentmatt.com/), and then to QMLJS by Ad5001. &lt;email@matthewcrumley.com&gt; (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). under the [MIT License](https://raw.githubusercontent.com/silentmatt/expr-eval/master/LICENSE.txt).

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,2 +1,2 @@
#!/bin/bash #!/bin/bash
pyside6-lrelease *.ts lrelease *.ts

View file

@ -21,7 +21,7 @@ replace() {
rm ../qml/eu/ad5001/LogarithmPlotter/js/index.mjs # Remove index which should not be scanned 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 for file in $files; do
echo "Moving '$file' to '${file%.*}.js'..." echo "Moving '$file' to '${file%.*}.js'..."
mv "$file" "${file%.*}.js" mv "$file" "${file%.*}.js"
@ -33,14 +33,12 @@ for file in $files; do
replace "${file%.*}.js" "^export" "/*export*/" replace "${file%.*}.js" "^export" "/*export*/"
replace "${file%.*}.js" "async " "/*async */" replace "${file%.*}.js" "async " "/*async */"
replace "${file%.*}.js" "await" "/*await */" replace "${file%.*}.js" "await" "/*await */"
replace "${file%.*}.js" " #" "// #"
replace "${file%.*}.js" "this.#" "/*this.#*/"
done done
echo "----------------------------" echo "----------------------------"
echo "| Updating translations... |" echo "| Updating translations... |"
echo "----------------------------" 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 # Updating locations in files
for lp in *.ts; do for lp in *.ts; do
echo "Replacing locations in $lp..." echo "Replacing locations in $lp..."
@ -57,9 +55,7 @@ for file in $files; do
replace "$file" "/*async */" "async " replace "$file" "/*async */" "async "
replace "$file" "^/*export*/" "export" replace "$file" "^/*export*/" "export"
replace "$file" "^/*export default*/" "export default" replace "$file" "^/*export default*/" "export default"
replace "$file" '.mjs"*/' '.mjs"'
replace "$file" "^/*import" "import" replace "$file" "^/*import" "import"
replace "$file" "^/*export" "export" replace "$file" "^/*export" "export"
replace "$file" "// #" " #" replace "$file" '.mjs"*/$' '.mjs"'
replace "$file" "/*this.#*/" "this.#"
done done

View file

@ -1 +0,0 @@
../common/appearance.svg

View file

@ -1 +0,0 @@
../common/appearance.svg

View file

@ -1 +0,0 @@
../common/arrow.svg

View file

@ -1 +0,0 @@
../common/position.svg

View file

@ -1 +0,0 @@
../common/angle.svg

View file

@ -1 +0,0 @@
../common/angle.svg

View file

@ -1 +0,0 @@
../common/appearance.svg

View file

@ -1 +0,0 @@
../common/target.svg

View file

@ -1 +0,0 @@
../common/position.svg

View file

@ -1 +0,0 @@
../common/label.svg

View file

@ -1 +0,0 @@
../common/angle.svg

View file

@ -1 +0,0 @@
../common/position.svg

View file

@ -1 +0,0 @@
../common/position.svg

View file

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -0,0 +1 @@
../../common/appearance.svg

View file

@ -0,0 +1 @@
../../common/appearance.svg

View file

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -0,0 +1 @@
../../common/arrow.svg

View file

@ -0,0 +1 @@
../../common/position.svg

View file

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1 @@
../../common/angle.svg

View file

@ -0,0 +1 @@
../../common/appearance.svg

View file

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1 @@
../../common/target.svg

View file

@ -0,0 +1 @@
../../common/position.svg

View file

@ -0,0 +1 @@
../../common/label.svg

View file

@ -0,0 +1 @@
../../common/angle.svg

View file

@ -0,0 +1 @@
../../common/position.svg

View file

@ -0,0 +1 @@
../../common/position.svg

View file

@ -0,0 +1 @@
../../common/angle.svg

View file

@ -1,171 +1,64 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg <svg
width="48.0px" width="24.0px"
height="48.0px" height="24.0px"
viewBox="0 0 48.0 48.0" viewBox="0 0 24.0 24.0"
version="1.1" version="1.1"
id="SVGRoot" id="SVGRoot"
xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:svg="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:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#" xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"> xmlns:dc="http://purl.org/dc/elements/1.1/"><title
<title id="title836">LogarithmPlotter Icon v1.0</title><defs
id="title38896">LogarithmPlotter Icon</title> id="defs833" /><metadata
<defs id="metadata836"><rdf:RDF><cc:Work
id="defs2254"> rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
<linearGradient rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title>LogarithmPlotter Icon v1.0</dc:title><cc:license
id="linearGradient27593"> 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
<stop rdf:about="http://creativecommons.org/licenses/by-nc-sa/4.0/"><cc:permits
style="stop-color:#000000;stop-opacity:0.15000001;" rdf:resource="http://creativecommons.org/ns#Reproduction" /><cc:permits
offset="0" rdf:resource="http://creativecommons.org/ns#Distribution" /><cc:requires
id="stop27589" /> rdf:resource="http://creativecommons.org/ns#Notice" /><cc:requires
<stop rdf:resource="http://creativecommons.org/ns#Attribution" /><cc:prohibits
style="stop-color:#000000;stop-opacity:0;" rdf:resource="http://creativecommons.org/ns#CommercialUse" /><cc:permits
offset="1" rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /><cc:requires
id="stop27591" /> rdf:resource="http://creativecommons.org/ns#ShareAlike" /></cc:License></rdf:RDF></metadata><g
</linearGradient> id="layer2"
<linearGradient transform="matrix(1,0,0,0.94444444,0,1.1666667)"
id="linearGradient13467"> style="fill:#666666"><rect
<stop style="fill:#666666;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
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"
id="rect1546" id="rect1546"
width="18" width="18"
height="18.105883" height="18"
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"
x="3" x="3"
y="3" y="3"
ry="2.3212669" ry="2.25" /></g><g
rx="2.2499998" id="layer2-6"
transform="matrix(2.2222222,0,0,2.1539961,-2.6666667,-2.4619883)" /> transform="matrix(1,0,0,0.94444444,0,0.16666668)"
</g> style="fill:#f9f9f9"><rect
<g style="fill:#f9f9f9;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="layer3" id="rect1546-7"
style="fill:#0000ff"> width="18"
<path height="18"
id="path27475" x="3"
style="fill:url(#linearGradient27595);fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" y="3"
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" /> ry="2.25" /></g><g
</g> id="layer1"
<g style="stroke-width:2;stroke-dasharray:none"><rect
id="layer1-6"
style="stroke-width:2;stroke-dasharray:none"
transform="matrix(2,0,0,2,0,1)">
<rect
style="fill:#000000;fill-rule:evenodd;stroke-width:1.86898;stroke-dasharray:none;stroke-opacity:0" style="fill:#000000;fill-rule:evenodd;stroke-width:1.86898;stroke-dasharray:none;stroke-opacity:0"
id="rect1410" id="rect1410"
width="14" width="14"
height="2" height="2"
x="5" x="5"
y="15.5" /> y="15.5" /><rect
<rect style="fill:#000000;fill-rule:evenodd;stroke-width:2;stroke-dasharray:none;stroke-opacity:0"
style="fill:#000000;fill-rule:evenodd;stroke-width:2.06559;stroke-dasharray:none;stroke-opacity:0"
id="rect1412" id="rect1412"
width="2" width="2"
height="16" height="15"
x="8" x="9"
y="3.5" /> y="3.9768662" /><path
<path
style="fill:none;fill-rule:evenodd;stroke:#ff0000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" style="fill:none;fill-rule:evenodd;stroke:#ff0000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path1529" id="path1529"
d="m 18,3.5 c 0,7 -4,12 -13,12" /> d="M 18,4 C 18,10.017307 13.40948,15.5 5,15.5" /></g></svg>
</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 &lt;mail@ad5001.eu&gt;</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>

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -3,7 +3,7 @@ Source: logarithmplotter
Version: 0.6.0 Version: 0.6.0
Architecture: all Architecture: all
Maintainer: Ad5001 <mail@ad5001.eu> 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 Build-Depends: debhelper (>=11~), dh-python, dpkg-dev (>= 1.16.1~), python-setuptools
Section: science Section: science

View file

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

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2025 Ad5001 --> <!-- Copyright 2024 Ad5001 -->
<component type="desktop-application"> <component type="desktop-application">
<id>eu.ad5001.LogarithmPlotter</id> <id>eu.ad5001.LogarithmPlotter</id>
<launchable type="desktop-id">logarithmplotter.desktop</launchable> <launchable type="desktop-id">logarithmplotter.desktop</launchable>
@ -66,54 +66,50 @@
<categories> <categories>
<category>Science</category> <category>Science</category>
<category>Education</category> <category>Education</category>
<category>Qt</category>
</categories> </categories>
<url type="homepage">https://apps.ad5001.eu/logarithmplotter/</url> <url type="homepage">https://apps.ad5001.eu/logarithmplotter/</url>
<url type="bugtracker">https://git.ad5001.eu/Ad5001/LogarithmPlotter/issues/</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="help">https://git.ad5001.eu/Ad5001/LogarithmPlotter/wiki/</url>
<url type="translate">https://hosted.weblate.org/engage/logarithmplotter/</url> <url type="translate">https://hosted.weblate.org/engage/logarithmplotter/</url>
<screenshots> <screenshots>
<screenshot type="default"> <screenshot type="default">
<image>https://apps.ad5001.eu/img/en/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.6</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.6</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.6</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.6</image> <image xml:lang="no">https://apps.ad5001.eu/img/no/logarithmplotter/gain.png?v=0.5</image>
<image xml:lang="es">https://apps.ad5001.eu/img/es/logarithmplotter/gain.png?v=0.6</image>
<caption>Main view of LogarithmPlotter showing an asymptotic Bode magnitude plot.</caption> <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="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="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="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="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>
<screenshot> <screenshot>
<image>https://apps.ad5001.eu/img/en/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.6</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.6</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.6</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.6</image> <image xml:lang="no">https://apps.ad5001.eu/img/no/logarithmplotter/phase.png?v=0.5</image>
<image xml:lang="es">https://apps.ad5001.eu/img/es/logarithmplotter/phase.png?v=0.6</image>
<caption>Main view of LogarithmPlotter showing an asymptotic Bode phase plot.</caption> <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="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="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="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="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>
<screenshot> <screenshot>
<image>https://apps.ad5001.eu/img/en/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.6</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.6</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.6</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.6</image> <image xml:lang="no">https://apps.ad5001.eu/img/no/logarithmplotter/welcome.png?v=0.5</image>
<image xml:lang="es">https://apps.ad5001.eu/img/es/logarithmplotter/welcome.png?v=0.6</image>
<caption>LogarithmPlotter's welcome page.</caption> <caption>LogarithmPlotter's welcome page.</caption>
<caption xml:lang="de">LogarithmPlotter's Willkommensseite.</caption> <caption xml:lang="de">LogarithmPlotter's Willkommensseite.</caption>
<caption xml:lang="fr">Page d'accueil de LogarithmPlotter.</caption> <caption xml:lang="fr">Page d'accueil de LogarithmPlotter.</caption>
<caption xml:lang="hu">LogarithmPlotter üdvözlő oldala.</caption> <caption xml:lang="hu">LogarithmPlotter üdvözlő oldala.</caption>
<caption xml:lang="no">LogarithmPlotters velkomstside.</caption> <caption xml:lang="no">LogarithmPlotters velkomstside.</caption>
<caption xml:lang="es">Página de bienvenida de LogarithmPlotter.</caption>
</screenshot> </screenshot>
</screenshots> </screenshots>

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info"> <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"> <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> <comment xml:lang="fr">Fichier Graphe Logarithmique</comment>
<icon name="application-x-logarithm-plot"/> <icon name="application-x-logarithm-plot"/>
<glob-deleteall/> <glob-deleteall/>

View file

@ -13,7 +13,7 @@ Unicode True
!define WEBSITE "https://apps.ad5001.eu/logarithmplotter" !define WEBSITE "https://apps.ad5001.eu/logarithmplotter"
!define VERSION_SHORT "0.6.0" !define VERSION_SHORT "0.6.0"
!define APP_VERSION "${VERSION_SHORT}.0" !define APP_VERSION "${VERSION_SHORT}.0"
!define COPYRIGHT "Ad5001 (c) 2021-2025" !define COPYRIGHT "Ad5001 (c) 2021-2024"
!define DESCRIPTION "Create graphs with logarithmic scales." !define DESCRIPTION "Create graphs with logarithmic scales."
!define REG_UNINSTALL "Software\Microsoft\Windows\CurrentVersion\Uninstall\LogarithmPlotter" !define REG_UNINSTALL "Software\Microsoft\Windows\CurrentVersion\Uninstall\LogarithmPlotter"

View file

@ -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"}

View file

@ -12,29 +12,32 @@ steps:
- git submodule update --init --recursive - git submodule update --init --recursive
- name: Build - name: Build
image: ad5001/ubuntu-pyside-xvfb:linux-6-latest-latex-node image: node:18-bookworm
commands: commands:
- cd common && npm install && cd .. - cd common && npm install && cd ..
- apt update
- apt install -y qtchooser qttools5-dev-tools
# Start building
- bash scripts/build.sh - bash scripts/build.sh
when:
event: [ push, tag ]
- name: Unit Tests - name: Unit Tests
image: ad5001/ubuntu-pyside-xvfb:linux-6-latest-latex-node image: ad5001/ubuntu-pyside-xvfb:linux-6-latest-latex
commands: commands:
- apt update
- apt install -y npm
- cd common && npm install -D && cd .. - cd common && npm install -D && cd ..
- xvfb-run bash scripts/run-tests.sh --no-rebuild - xvfb-run bash scripts/run-tests.sh --no-rebuild
when: when:
event: [ push, tag ] event: [ push, tag ]
- name: File Tests - name: File Tests
image: ad5001/ubuntu-pyside-xvfb:linux-6-latest-latex-node image: ad5001/ubuntu-pyside-xvfb:linux-6-latest-latex
commands: commands:
- xvfb-run python3 run.py --test-build --no-check-for-updates - 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/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/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: when:
event: [ push, tag ] event: [ push, tag ]

File diff suppressed because one or more lines are too long

View file

@ -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"}

File diff suppressed because one or more lines are too long

1539
common/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@
"name": "logarithmplotter", "name": "logarithmplotter",
"version": "0.6.0", "version": "0.6.0",
"description": "2D plotter software to make Bode plots, sequences and distribution functions.", "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": { "scripts": {
"build": "rollup --config rollup.config.mjs", "build": "rollup --config rollup.config.mjs",
"test": "c8 mocha test/**/*.mjs" "test": "c8 mocha test/**/*.mjs"
@ -24,12 +24,9 @@
}, },
"devDependencies": { "devDependencies": {
"@types/chai": "^5.0.0", "@types/chai": "^5.0.0",
"@types/chai-spies": "^1.0.6",
"@types/chai-as-promised": "^8.0.1",
"@types/mocha": "^10.0.8", "@types/mocha": "^10.0.8",
"chai": "^5.1.1", "chai": "^5.1.1",
"chai-as-promised": "^8.0.0", "chai-as-promised": "^8.0.0",
"chai-spies": "^1.1.0",
"esm": "^3.2.25", "esm": "^3.2.25",
"mocha": "^10.7.3" "mocha": "^10.7.3"
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -22,7 +22,7 @@ import { babel } from "@rollup/plugin-babel"
import cleanup from "rollup-plugin-cleanup" import cleanup from "rollup-plugin-cleanup"
const src = "./src/index.mjs" 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 { export default {
input: src, input: src,

View file

@ -1,116 +0,0 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 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)
}
}

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -95,15 +95,11 @@ export class Action {
if(!Latex.enabled) if(!Latex.enabled)
throw new Error("Cannot render an item as LaTeX when LaTeX is disabled.") throw new Error("Cannot render an item as LaTeX when LaTeX is disabled.")
const imgDepth = History.imageDepth const imgDepth = History.imageDepth
const renderArguments = [ const { source, width, height } = await Latex.requestAsyncRender(
latexString, latexString,
imgDepth * (History.fontSize + 2), imgDepth * (History.fontSize + 2),
History.themeTextColor 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"/>` return `<img src="${source}" width="${width / imgDepth}" height="${height / imgDepth}" style="vertical-align: middle"/>`
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -19,7 +19,7 @@
import Objects from "../module/objects.mjs" import Objects from "../module/objects.mjs"
import Latex from "../module/latex.mjs" import Latex from "../module/latex.mjs"
import * as MathLib from "../math/index.mjs" import * as MathLib from "../math/index.mjs"
import { escapeHTML } from "../utils/index.mjs" import { escapeHTML } from "../utils.mjs"
import { Action } from "./common.mjs" import { Action } from "./common.mjs"
/** /**

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -18,11 +18,10 @@
import js from "./lib/polyfills/js.mjs" 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" import * as ObjsAutoload from "./objs/autoload.mjs"
export * as Modules from "./module/index.mjs"
export * as MathLib from "./math/index.mjs" export * as MathLib from "./math/index.mjs"
export * as HistoryLib from "./history/index.mjs" export * as HistoryLib from "./history/index.mjs"
export * as Parsing from "./parsing/index.mjs" export * as Parsing from "./parsing/index.mjs"
export * as Utils from "./utils.mjs"

View file

@ -7,7 +7,7 @@
* *
* Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu) * Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu)
* *
* Copyright (c) 2015 Matthew Crumley, 2021-2025 Ad5001 * Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
@ -111,7 +111,7 @@ function simplify(tokens, unaryOps, binaryOps, ternaryOps, values) {
* In the given instructions, replaces variable by expr. * In the given instructions, replaces variable by expr.
* @param {Instruction[]} tokens * @param {Instruction[]} tokens
* @param {string} variable * @param {string} variable
* @param {ExprEvalExpression} expr * @param {number} expr
* @return {Instruction[]} * @return {Instruction[]}
*/ */
function substitute(tokens, variable, expr) { function substitute(tokens, variable, expr) {
@ -171,6 +171,9 @@ function evaluate(tokens, expr, values) {
nstack.push(n1 ? !!evaluate(n2, expr, values) : false) nstack.push(n1 ? !!evaluate(n2, expr, values) : false)
} else if(item.value === "or") { } else if(item.value === "or") {
nstack.push(n1 ? true : !!evaluate(n2, expr, values)) 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 { } else {
f = expr.binaryOps[item.value] f = expr.binaryOps[item.value]
nstack.push(f(resolveExpression(n1, values), resolveExpression(n2, values))) nstack.push(f(resolveExpression(n1, values), resolveExpression(n2, values)))
@ -487,6 +490,18 @@ export class ExprEvalExpression {
return evaluate(this.tokens, this, values) 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() { toString() {
return expressionToString(this.tokens, false) return expressionToString(this.tokens, false)
} }

View file

@ -7,7 +7,7 @@
* *
* Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu) * Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu)
* *
* Copyright (c) 2015 Matthew Crumley, 2021-2025 Ad5001 * Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

View file

@ -7,7 +7,7 @@
* *
* Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu) * Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu)
* *
* Copyright (c) 2015 Matthew Crumley, 2021-2025 Ad5001 * Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
@ -47,7 +47,9 @@ const optionNameMap = {
"not": "logical", "not": "logical",
"?": "conditional", "?": "conditional",
":": "conditional", ":": "conditional",
//'=': 'assignment', // Disable assignment
"[": "array" "[": "array"
//'()=': 'fndef' // Diable function definition
} }
export class Parser { export class Parser {
@ -107,6 +109,7 @@ export class Parser {
and: Polyfill.andOperator, and: Polyfill.andOperator,
or: Polyfill.orOperator, or: Polyfill.orOperator,
"in": Polyfill.inOperator, "in": Polyfill.inOperator,
"=": Polyfill.setVar,
"[": Polyfill.arrayIndex "[": Polyfill.arrayIndex
} }
@ -120,13 +123,18 @@ export class Parser {
min: Polyfill.min, min: Polyfill.min,
max: Polyfill.max, max: Polyfill.max,
hypot: Math.hypot || Polyfill.hypot, hypot: Math.hypot || Polyfill.hypot,
pyt: Math.hypot || Polyfill.hypot, pyt: Math.hypot || Polyfill.hypot, // backward compat
pow: Math.pow, pow: Math.pow,
atan2: Math.atan2, atan2: Math.atan2,
"if": Polyfill.condition, "if": Polyfill.condition,
gamma: Polyfill.gamma, gamma: Polyfill.gamma,
"Γ": Polyfill.gamma, "Γ": Polyfill.gamma,
roundTo: Polyfill.roundTo, 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. // These constants will automatically be replaced the MOMENT they are parsed.
@ -151,6 +159,10 @@ export class Parser {
return new ExprEvalExpression(instr, this) return new ExprEvalExpression(instr, this)
} }
evaluate(expr, variables) {
return this.parse(expr).evaluate(variables)
}
isOperatorEnabled(op) { isOperatorEnabled(op) {
const optionName = optionNameMap.hasOwnProperty(op) ? optionNameMap[op] : op const optionName = optionNameMap.hasOwnProperty(op) ? optionNameMap[op] : op
const operators = this.options.operators || {} const operators = this.options.operators || {}

View file

@ -7,7 +7,7 @@
* *
* Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu) * Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu)
* *
* Copyright (c) 2015 Matthew Crumley, 2021-2025 Ad5001 * Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

View file

@ -7,7 +7,7 @@
* *
* Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu) * Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu)
* *
* Copyright (c) 2015 Matthew Crumley, 2021-2025 Ad5001 * Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
@ -210,8 +210,9 @@ export function gamma(n) {
} }
export function stringOrArrayLength(s) { export function stringOrArrayLength(s) {
if(Array.isArray(s)) if(Array.isArray(s)) {
return s.length return s.length
}
return String(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)) 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) { export function arrayIndex(array, index) {
return array[index | 0] 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) { export function sign(x) {
return ((x > 0) - (x < 0)) || +x return ((x > 0) - (x < 0)) || +x
} }

View file

@ -7,7 +7,7 @@
* *
* Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu) * Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu)
* *
* Copyright (c) 2015 Matthew Crumley, 2021-2025 Ad5001 * Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
@ -472,7 +472,7 @@ export class TokenStream {
this.current = this.newToken(TOP, "==") this.current = this.newToken(TOP, "==")
this.pos++ this.pos++
} else { } else {
return false this.current = this.newToken(TOP, c)
} }
} else if(c === "!") { } else if(c === "!") {
if(this.expression.charAt(this.pos + 1) === "=") { if(this.expression.charAt(this.pos + 1) === "=") {

View file

@ -1,6 +1,6 @@
/*! /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -64,20 +64,10 @@ function arrayFlatMap(callbackFn, thisArg) {
* @return {String} * @return {String}
*/ */
function stringReplaceAll(from, to) { function stringReplaceAll(from, to) {
return this.split(from).join(to) let str = this
} while(str.includes(from))
str = str.replace(from, to)
/** return str
* 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]
} }
@ -108,8 +98,8 @@ const polyfills = {
[String.prototype, "replaceAll", stringReplaceAll] [String.prototype, "replaceAll", stringReplaceAll]
], ],
2022: [ 2022: [
[Array.prototype, "at", arrayAt], [Array.prototype, "at", notPolyfilled("Array.prototype.at")],
[String.prototype, "at", arrayAt], [String.prototype, "at", notPolyfilled("String.prototype.at")],
[Object, "hasOwn", notPolyfilled("Object.hasOwn")] [Object, "hasOwn", notPolyfilled("Object.hasOwn")]
], ],
2023: [ 2023: [
@ -119,7 +109,7 @@ const polyfills = {
[Array.prototype, "toSpliced", notPolyfilled("Array.prototype.toSpliced")], [Array.prototype, "toSpliced", notPolyfilled("Array.prototype.toSpliced")],
[Array.prototype, "with", notPolyfilled("Array.prototype.with")] [Array.prototype, "with", notPolyfilled("Array.prototype.with")]
], ],
2025: [ 2024: [
[Object, "groupBy", notPolyfilled("Object.groupBy")], [Object, "groupBy", notPolyfilled("Object.groupBy")],
[Map, "groupBy", notPolyfilled("Map.groupBy")] [Map, "groupBy", notPolyfilled("Map.groupBy")]
] ]
@ -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)) { for(const [context, functionName, polyfill] of entries.filter(x => x[0][x[1]] === undefined)) {
context[functionName] = polyfill context[functionName] = polyfill
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -24,7 +24,6 @@ import { Expression, executeExpression } from "./expression.mjs"
*/ */
export class Domain { export class Domain {
constructor() { constructor() {
this.latexMarkup = "#INVALID"
} }
/** /**
@ -207,8 +206,8 @@ export class Range extends Domain {
} }
includes(x) { includes(x) {
if(typeof x == "string") x = executeExpression(x)
if(x instanceof Expression) x = x.execute() if(x instanceof Expression) x = x.execute()
if(typeof x == "string") x = executeExpression(x)
return ((this.openBegin && x > this.begin.execute()) || (!this.openBegin && x >= this.begin.execute())) && return ((this.openBegin && x > this.begin.execute()) || (!this.openBegin && x >= this.begin.execute())) &&
((this.openEnd && x < this.end.execute()) || (!this.openEnd && x <= this.end.execute())) ((this.openEnd && x < this.end.execute()) || (!this.openEnd && x <= this.end.execute()))
} }
@ -250,17 +249,15 @@ export class SpecialDomain extends Domain {
/** /**
* @constructs SpecialDomain * @constructs SpecialDomain
* @param {string} displayName * @param {string} displayName
* @param {string} latexMarkup - markup representing the domain.
* @param {function} isValid - function returning true when number is in domain false when it isn't. * @param {function} isValid - function returning true when number is in domain false when it isn't.
* @param {function} next - function provides the next positive value in the domain after the one given. * @param {function} next - function provides the next positive value in the domain after the one given.
* @param {function} previous - function provides the previous positive value in the domain before the one given. * @param {function} previous - function provides the previous positive value in the domain before the one given.
* @param {boolean} moveSupported - Only true if next and previous functions are valid. * @param {boolean} moveSupported - Only true if next and previous functions are valid.
*/ */
constructor(displayName, latexMarkup, isValid, next = () => true, previous = () => true, constructor(displayName, isValid, next = () => true, previous = () => true,
moveSupported = true) { moveSupported = true) {
super() super()
this.displayName = displayName this.displayName = displayName
this.latexMarkup = latexMarkup
this.isValid = isValid this.isValid = isValid
this.nextValue = next this.nextValue = next
this.prevValue = previous this.prevValue = previous
@ -565,54 +562,39 @@ Domain.RPE.latexMarkup = "\\mathbb{R}^{+*}"
Domain.RME = new Range(-Infinity, 0, true, true) Domain.RME = new Range(-Infinity, 0, true, true)
Domain.RME.displayName = "ℝ⁻*" Domain.RME.displayName = "ℝ⁻*"
Domain.RME.latexMarkup = "\\mathbb{R}^{+*}" Domain.RME.latexMarkup = "\\mathbb{R}^{+*}"
Domain.N = new SpecialDomain( Domain.N = new SpecialDomain("", x => x % 1 === 0 && x >= 0,
"", "\\mathbb{N}",
x => x % 1 === 0 && x >= 0,
x => Math.max(Math.floor(x) + 1, 0), x => Math.max(Math.floor(x) + 1, 0),
x => Math.max(Math.ceil(x) - 1, 0) x => Math.max(Math.ceil(x) - 1, 0))
) Domain.N.latexMarkup = "\\mathbb{N}"
Domain.NE = new SpecialDomain( Domain.NE = new SpecialDomain("*", x => x % 1 === 0 && x > 0,
"*", "\\mathbb{N}^{*}",
x => x % 1 === 0 && x > 0,
x => Math.max(Math.floor(x) + 1, 1), x => Math.max(Math.floor(x) + 1, 1),
x => Math.max(Math.ceil(x) - 1, 1) x => Math.max(Math.ceil(x) - 1, 1))
) Domain.NE.latexMarkup = "\\mathbb{N}^{*}"
Domain.Z = new SpecialDomain( Domain.Z = new SpecialDomain("", x => x % 1 === 0, x => Math.floor(x) + 1, x => Math.ceil(x) - 1)
"", "\\mathbb{Z}", Domain.Z.latexMarkup = "\\mathbb{Z}"
x => x % 1 === 0, Domain.ZE = new SpecialDomain("*", x => x % 1 === 0 && x !== 0,
x => Math.floor(x) + 1,
x => Math.ceil(x) - 1
)
Domain.ZE = new SpecialDomain(
"*", "\\mathbb{Z}^{*}",
x => x % 1 === 0 && x !== 0,
x => Math.floor(x) + 1 === 0 ? Math.floor(x) + 2 : Math.floor(x) + 1, x => Math.floor(x) + 1 === 0 ? Math.floor(x) + 2 : Math.floor(x) + 1,
x => Math.ceil(x) - 1 === 0 ? Math.ceil(x) - 2 : Math.ceil(x) - 1 x => Math.ceil(x) - 1 === 0 ? Math.ceil(x) - 2 : Math.ceil(x) - 1)
) Domain.ZE.latexMarkup = "\\mathbb{Z}^{*}"
Domain.ZM = new SpecialDomain( Domain.ZM = new SpecialDomain("ℤ⁻", x => x % 1 === 0 && x <= 0,
"ℤ⁻", "\\mathbb{Z}^{-}",
x => x % 1 === 0 && x <= 0,
x => Math.min(Math.floor(x) + 1, 0), x => Math.min(Math.floor(x) + 1, 0),
x => Math.min(Math.ceil(x) - 1, 0) x => Math.min(Math.ceil(x) - 1, 0))
) Domain.ZM.latexMarkup = "\\mathbb{Z}^{-}"
Domain.ZME = new SpecialDomain( Domain.ZME = new SpecialDomain("ℤ⁻*", x => x % 1 === 0 && x < 0,
"ℤ⁻*", "\\mathbb{Z}^{-*}",
x => x % 1 === 0 && x < 0,
x => Math.min(Math.floor(x) + 1, -1), x => Math.min(Math.floor(x) + 1, -1),
x => Math.min(Math.ceil(x) - 1, -1) x => Math.min(Math.ceil(x) - 1, -1))
) Domain.ZME.latexMarkup = "\\mathbb{Z}^{-*}"
Domain.NLog = new SpecialDomain( Domain.NLog = new SpecialDomain("ℕˡᵒᵍ",
"ℕˡᵒᵍ", "\\mathbb{N}^{log}",
x => x / Math.pow(10, Math.ceil(Math.log10(x))) % 1 === 0 && x > 0, x => x / Math.pow(10, Math.ceil(Math.log10(x))) % 1 === 0 && x > 0,
x => { function(x) {
let x10pow = Math.pow(10, Math.ceil(Math.log10(x))) let x10pow = Math.pow(10, Math.ceil(Math.log10(x)))
return Math.max(1, (Math.floor(x / x10pow) + 1) * x10pow) return Math.max(1, (Math.floor(x / x10pow) + 1) * x10pow)
}, },
x => { function(x) {
let x10pow = Math.pow(10, Math.ceil(Math.log10(x))) let x10pow = Math.pow(10, Math.ceil(Math.log10(x)))
return Math.max(1, (Math.ceil(x / x10pow) - 1) * x10pow) return Math.max(1, (Math.ceil(x / x10pow) - 1) * x10pow)
} })
) Domain.NLog.latexMarkup = "\\mathbb{N}^{log}"
let refedDomains = [] let refedDomains = []
@ -644,7 +626,7 @@ export function parseDomainSimple(domain) {
if(domain.includes("U") || domain.includes("")) return UnionDomain.import(domain) if(domain.includes("U") || domain.includes("")) return UnionDomain.import(domain)
if(domain.includes("∩")) return IntersectionDomain.import(domain) if(domain.includes("∩")) return IntersectionDomain.import(domain)
if(domain.includes("") || domain.includes("\\")) return MinusDomain.import(domain) if(domain.includes("") || domain.includes("\\")) return MinusDomain.import(domain)
if(domain.at(0) === "{" && domain.at(-1) === "}") return DomainSet.import(domain) if(domain.charAt(0) === "{" && domain.charAt(domain.length - 1) === "}") return DomainSet.import(domain)
if(domain.includes("]") || domain.includes("[")) return Range.import(domain) if(domain.includes("]") || domain.includes("[")) return Range.import(domain)
if(["R", "", "N", "", "Z", ""].some(str => domain.toUpperCase().includes(str))) if(["R", "", "N", "", "Z", ""].some(str => domain.toUpperCase().includes(str)))
return Domain.import(domain) return Domain.import(domain)

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -17,38 +17,28 @@
*/ */
import * as Utils from "../utils/index.mjs" import * as Utils from "../utils.mjs"
import { ExprEvalExpression } from "../lib/expr-eval/expression.mjs"
import Latex from "../module/latex.mjs" import Latex from "../module/latex.mjs"
import ExprParser from "../module/expreval.mjs" import ExprParser from "../module/expreval.mjs"
import Objects from "../module/objects.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. * Represents any kind of x-based or non variable based expression.
*/ */
export class Expression { export class Expression {
/**
*
* @param {string|ExprEvalExpression} expr
*/
constructor(expr) { constructor(expr) {
if(typeof expr === "string") { if(typeof expr === "string") {
this.expr = Utils.exponentsToExpression(expr) this.expr = Utils.exponentsToExpression(expr)
this.calc = ExprParser.parse(this.expr).simplify() this.calc = ExprParser.parse(this.expr).simplify()
} else if(expr instanceof ExprEvalExpression) { } else {
// Passed an expression here directly. // Passed an expression here directly.
this.calc = expr.simplify() this.calc = expr.simplify()
this.expr = expr.toString() 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 this.cachedValue = null
if(this.canBeCached && this.allRequirementsFulfilled()) if(this.cached && this.allRequirementsFulfilled())
this.recache() this.cachedValue = this.calc.evaluate(Objects.currentObjectsByName)
this.latexMarkup = Latex.expression(this.calc.tokens) 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. * Returns a list of names whose corresponding objects this expression is dependant on and are missing.
* @return {string[]} * @return {boolean}
*/ */
undefinedVariables() { undefinedVariables() {
return this.requiredObjects().filter(objName => !(objName in Objects.currentObjectsByName)) return this.requiredObjects().filter(objName => !(objName in Objects.currentObjectsByName))
} }
recache() { recache() {
this.cachedValue = this.calc.evaluate(Objects.currentObjectsByName) if(this.cached)
this.cachedValue = this.calc.evaluate(Objects.currentObjectsByName)
} }
execute(x = 1) { execute(x = 1) {
if(this.canBeCached) { if(this.cached) {
if(this.cachedValue == null) if(this.cachedValue == null)
this.recache() this.cachedValue = this.calc.evaluate(Objects.currentObjectsByName)
return this.cachedValue return this.cachedValue
} }
ExprParser.currentVars = Object.assign({ "x": x }, Objects.currentObjectsByName) ExprParser.currentVars = Object.assign({ "x": x }, Objects.currentObjectsByName)
@ -108,10 +99,9 @@ export class Expression {
} }
simplify(x) { simplify(x) {
let expr = new Expression(this.calc.substitute("x", x).simplify()) let expr = this.calc.substitute("x", x).simplify()
if(expr.allRequirementsFulfilled() && expr.execute() === 0) if(expr.evaluate() === 0) expr = "0"
expr = new Expression("0") return new Expression(expr)
return expr
} }
toEditableString() { toEditableString() {
@ -120,28 +110,17 @@ export class Expression {
toString(forceSign = false) { toString(forceSign = false) {
let str = Utils.makeExpressionReadable(this.calc.toString()) let str = Utils.makeExpressionReadable(this.calc.toString())
if(str !== undefined && str.match(NUMBER_MATCHER)) { if(str !== undefined && str.match(/^\d*\.\d+$/)) {
const decimals = str.split(".")[1].split("e")[0] if(str.split(".")[1].split("0").length > 7) {
const zeros = decimals.split("0").length
const nines = decimals.split("9").length
if(zeros > 7 || nines > 7) {
// Likely rounding error // Likely rounding error
str = parseFloat(str).toDecimalPrecision(8).toString() str = parseFloat(str.substring(0, str.length - 1)).toString()
} }
} }
if(str[0] === "(" && str.at(-1) === ")") if(str[0] !== "-" && forceSign) str = "+" + str
str = str.substring(1, str.length - 1)
if(str[0] !== "-" && forceSign)
str = "+" + str
return str return str
} }
} }
/**
* Parses and executes the given expression
* @param {string} expr
* @return {number}
*/
export function executeExpression(expr) { export function executeExpression(expr) {
return (new Expression(expr.toString())).execute() return (new Expression(expr.toString())).execute()
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -17,7 +17,7 @@
*/ */
import * as Expr from "./expression.mjs" 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 Latex from "../module/latex.mjs"
import Objects from "../module/objects.mjs" import Objects from "../module/objects.mjs"
import ExprParser from "../module/expreval.mjs" import ExprParser from "../module/expreval.mjs"

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -18,33 +18,30 @@
import { Module } from "./common.mjs" import { Module } from "./common.mjs"
import { CanvasInterface, DialogInterface } from "./interface.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 { Expression } from "../math/index.mjs"
import Latex from "./latex.mjs" import Latex from "./latex.mjs"
import Objects from "./objects.mjs" import Objects from "./objects.mjs"
import History from "./history.mjs" import History from "./history.mjs"
import Settings from "./settings.mjs"
class CanvasAPI extends Module { 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() { constructor() {
super("Canvas", { super("Canvas", {
canvas: CanvasInterface, canvas: CanvasInterface,
drawingErrorDialog: DialogInterface 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}>} * @type {Object.<string, {expression: Expression, value: number, maxDraw: number}>}
@ -70,18 +67,18 @@ class CanvasAPI extends Module {
*/ */
initialize({ canvas, drawingErrorDialog }) { initialize({ canvas, drawingErrorDialog }) {
super.initialize({ canvas, drawingErrorDialog }) super.initialize({ canvas, drawingErrorDialog })
this.#canvas = canvas this._canvas = canvas
this.#drawingErrorDialog = drawingErrorDialog this._drawingErrorDialog = drawingErrorDialog
} }
get width() { get width() {
if(!this.initialized) throw new Error("Attempting width before initialize!") if(!this.initialized) throw new Error("Attempting width before initialize!")
return this.#canvas.width return this._canvas.width
} }
get height() { get height() {
if(!this.initialized) throw new Error("Attempting height before initialize!") 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() { get xmin() {
if(!this.initialized) throw new Error("Attempting xmin before initialize!") 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() { get xzoom() {
if(!this.initialized) throw new Error("Attempting xzoom before initialize!") 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() { get ymax() {
if(!this.initialized) throw new Error("Attempting ymax before initialize!") 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() { get yzoom() {
if(!this.initialized) throw new Error("Attempting yzoom before initialize!") 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() { get xlabel() {
if(!this.initialized) throw new Error("Attempting xlabel before initialize!") 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() { get ylabel() {
if(!this.initialized) throw new Error("Attempting ylabel before initialize!") 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() { get linewidth() {
if(!this.initialized) throw new Error("Attempting linewidth before initialize!") 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() { get textsize() {
if(!this.initialized) throw new Error("Attempting textsize before initialize!") 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() { get logscalex() {
if(!this.initialized) throw new Error("Attempting logscalex before initialize!") 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() { get showxgrad() {
if(!this.initialized) throw new Error("Attempting showxgrad before initialize!") 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() { get showygrad() {
if(!this.initialized) throw new Error("Attempting showygrad before initialize!") 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() { requestPaint() {
if(!this.initialized) throw new Error("Attempting requestPaint before initialize!") if(!this.initialized) throw new Error("Attempting requestPaint before initialize!")
this.#canvas.requestPaint() this._canvas.requestPaint()
} }
/** /**
@ -212,19 +209,17 @@ class CanvasAPI extends Module {
*/ */
redraw() { redraw() {
if(!this.initialized) throw new Error("Attempting redraw before initialize!") if(!this.initialized) throw new Error("Attempting redraw before initialize!")
if(this.#ctx == null) this._ctx = this._canvas.getContext("2d")
this.#ctx = this.#canvas.getContext("2d")
this.#redrawCount = (this.#redrawCount + 1) % 10000
this._computeAxes() this._computeAxes()
this._reset() this._reset()
this._drawGrid() this._drawGrid()
this._drawAxes() this._drawAxes()
this._drawLabels() this._drawLabels()
this.#ctx.lineWidth = this.linewidth this._ctx.lineWidth = this.linewidth
for(let objType in Objects.currentObjects) { for(let objType in Objects.currentObjects) {
for(let obj of Objects.currentObjects[objType]) { for(let obj of Objects.currentObjects[objType]) {
this.#ctx.strokeStyle = obj.color this._ctx.strokeStyle = obj.color
this.#ctx.fillStyle = obj.color this._ctx.fillStyle = obj.color
if(obj.visible) if(obj.visible)
try { try {
obj.draw(this) obj.draw(this)
@ -232,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) // Drawing throws an error. Generally, it's due to a new modification (or the opening of a file)
console.error(e) console.error(e)
console.log(e.stack) console.log(e.stack)
this.#drawingErrorDialog.show(objType, obj.name, e.message) this._drawingErrorDialog.show(objType, obj.name, e.message)
History.undo() History.undo()
} }
} }
} }
this.#ctx.lineWidth = 1 this._ctx.lineWidth = 1
} }
/** /**
@ -245,9 +240,9 @@ class CanvasAPI extends Module {
* @private * @private
*/ */
_computeAxes() { _computeAxes() {
let exprY = new Expression(`x*(${Settings.yaxisstep})`) let exprY = new Expression(`x*(${this._canvas.yaxisstep})`)
let y1 = exprY.execute(1) 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) let x1 = exprX.execute(1)
this.axesSteps = { this.axesSteps = {
x: { x: {
@ -269,10 +264,10 @@ class CanvasAPI extends Module {
*/ */
_reset() { _reset() {
// Reset // Reset
this.#ctx.fillStyle = "#FFFFFF" this._ctx.fillStyle = "#FFFFFF"
this.#ctx.strokeStyle = "#000000" this._ctx.strokeStyle = "#000000"
this.#ctx.font = `${this.textsize}px sans-serif` this._ctx.font = `${this.textsize}px sans-serif`
this.#ctx.fillRect(0, 0, this.width, this.height) this._ctx.fillRect(0, 0, this.width, this.height)
} }
/** /**
@ -280,7 +275,7 @@ class CanvasAPI extends Module {
* @private * @private
*/ */
_drawGrid() { _drawGrid() {
this.#ctx.strokeStyle = "#C0C0C0" this._ctx.strokeStyle = "#C0C0C0"
if(this.logscalex) { if(this.logscalex) {
for(let xpow = -this.maxgradx; xpow <= this.maxgradx; xpow++) { for(let xpow = -this.maxgradx; xpow <= this.maxgradx; xpow++) {
for(let xmulti = 1; xmulti < 10; xmulti++) { for(let xmulti = 1; xmulti < 10; xmulti++) {
@ -304,7 +299,7 @@ class CanvasAPI extends Module {
* @private * @private
*/ */
_drawAxes() { _drawAxes() {
this.#ctx.strokeStyle = "#000000" this._ctx.strokeStyle = "#000000"
let axisypos = this.logscalex ? 1 : 0 let axisypos = this.logscalex ? 1 : 0
this.drawXLine(axisypos) this.drawXLine(axisypos)
this.drawYLine(0) this.drawYLine(0)
@ -325,19 +320,19 @@ class CanvasAPI extends Module {
let axisypx = this.x2px(this.logscalex ? 1 : 0) // X coordinate of Y axis let axisypx = this.x2px(this.logscalex ? 1 : 0) // X coordinate of Y axis
let axisxpx = this.y2px(0) // Y coordinate of X axis let axisxpx = this.y2px(0) // Y coordinate of X axis
// Labels // Labels
this.#ctx.fillStyle = "#000000" this._ctx.fillStyle = "#000000"
this.#ctx.font = `${this.textsize}px sans-serif` this._ctx.font = `${this.textsize}px sans-serif`
this.#ctx.fillText(this.ylabel, axisypx + 10, 24) this._ctx.fillText(this.ylabel, axisypx + 10, 24)
let textWidth = this.#ctx.measureText(this.xlabel).width let textWidth = this._ctx.measureText(this.xlabel).width
this.#ctx.fillText(this.xlabel, this.width - 14 - textWidth, axisxpx - 5) this._ctx.fillText(this.xlabel, this.width - 14 - textWidth, axisxpx - 5)
// Axis graduation labels // 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.showxgrad) {
if(this.logscalex) { if(this.logscalex) {
for(let xpow = -this.maxgradx; xpow <= this.maxgradx; xpow += 1) { 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) if(xpow !== 0)
this.drawVisibleText("10" + textsup(xpow), this.x2px(Math.pow(10, xpow)) - textWidth / 2, axisxpx + 16 + (6 * (xpow === 1))) this.drawVisibleText("10" + textsup(xpow), this.x2px(Math.pow(10, xpow)) - textWidth / 2, axisxpx + 16 + (6 * (xpow === 1)))
} }
@ -355,13 +350,13 @@ class CanvasAPI extends Module {
for(let y = 0; y < this.axesSteps.y.maxDraw; y += 1) { for(let y = 0; y < this.axesSteps.y.maxDraw; y += 1) {
let drawY = y * this.axesSteps.y.value let drawY = y * this.axesSteps.y.value
let txtY = this.axesSteps.y.expression.simplify(y).toString().replace(/^\((.+)\)$/, "$1") 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))) this.drawVisibleText(txtY, axisypx - 6 - textWidth, this.y2px(drawY) + 4 + (10 * (y === 0)))
if(y !== 0) if(y !== 0)
this.drawVisibleText("-" + txtY, axisypx - 6 - textWidth - txtMinus, this.y2px(-drawY) + 4) this.drawVisibleText("-" + txtY, axisypx - 6 - textWidth - txtMinus, this.y2px(-drawY) + 4)
} }
} }
this.#ctx.fillStyle = "#FFFFFF" this._ctx.fillStyle = "#FFFFFF"
} }
// //
@ -399,7 +394,7 @@ class CanvasAPI extends Module {
drawVisibleText(text, x, y) { drawVisibleText(text, x, y) {
if(x > 0 && x < this.width && y > 0 && y < this.height) { if(x > 0 && x < this.width && y > 0 && y < this.height) {
text.toString().split("\n").forEach((txt, i) => { 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))
}) })
} }
} }
@ -414,8 +409,8 @@ class CanvasAPI extends Module {
* @param {number} height * @param {number} height
*/ */
drawVisibleImage(image, x, y, width, height) { drawVisibleImage(image, x, y, width, height) {
this.#canvas.markDirty(Qt.rect(x, y, width, height)) this._canvas.markDirty(Qt.rect(x, y, width, height))
this.#ctx.drawImage(image, x, y, width, height) this._ctx.drawImage(image, x, y, width, height)
} }
/** /**
@ -429,7 +424,7 @@ class CanvasAPI extends Module {
let defaultHeight = this.textsize * 1.2 // Approximate but good enough! let defaultHeight = this.textsize * 1.2 // Approximate but good enough!
for(let txt of text.split("\n")) { for(let txt of text.split("\n")) {
theight += defaultHeight 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 } return { "width": twidth, "height": theight }
} }
@ -499,10 +494,10 @@ class CanvasAPI extends Module {
* @param {number} y2 * @param {number} y2
*/ */
drawLine(x1, y1, x2, y2) { drawLine(x1, y1, x2, y2) {
this.#ctx.beginPath() this._ctx.beginPath()
this.#ctx.moveTo(x1, y1) this._ctx.moveTo(x1, y1)
this.#ctx.lineTo(x2, y2) this._ctx.lineTo(x2, y2)
this.#ctx.stroke() this._ctx.stroke()
} }
/** /**
@ -514,9 +509,9 @@ class CanvasAPI extends Module {
* @param {number} dashPxSize * @param {number} dashPxSize
*/ */
drawDashedLine(x1, y1, x2, y2, dashPxSize = 6) { 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.drawLine(x1, y1, x2, y2)
this.#ctx.setLineDash([]) this._ctx.setLineDash([])
} }
/** /**
@ -526,22 +521,14 @@ class CanvasAPI extends Module {
* @param {function(LatexRenderResult|{width: number, height: number, source: string})} callback * @param {function(LatexRenderResult|{width: number, height: number, source: string})} callback
*/ */
renderLatexImage(ltxText, color, callback) { renderLatexImage(ltxText, color, callback) {
const currentRedrawCount = this.#redrawCount
const onRendered = (imgData) => { 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. // Wait until the image is loaded to callback.
this.#canvas.loadImageAsync(imgData.source).then(() => { this._canvas.loadImage(imgData.source)
if(this.#redrawCount === currentRedrawCount) this._canvas.imageLoaders[imgData.source] = [callback, imgData]
callback(imgData)
else
console.log("1. Discard render of", imgData.source, this.#redrawCount, currentRedrawCount)
})
} else { } else {
// Callback directly // Callback directly
if(this.#redrawCount === currentRedrawCount) callback(imgData)
callback(imgData)
else
console.log("2. Discard render of", imgData.source, this.#redrawCount, currentRedrawCount)
} }
} }
const prerendered = Latex.findPrerendered(ltxText, this.textsize, color) const prerendered = Latex.findPrerendered(ltxText, this.textsize, color)
@ -556,11 +543,11 @@ class CanvasAPI extends Module {
// //
get font() { get font() {
return this.#ctx.font return this._ctx.font
} }
set font(value) { set font(value) {
return this.#ctx.font = value return this._ctx.font = value
} }
/** /**
@ -573,9 +560,9 @@ class CanvasAPI extends Module {
* @param {boolean} counterclockwise * @param {boolean} counterclockwise
*/ */
arc(x, y, radius, startAngle, endAngle, counterclockwise = false) { arc(x, y, radius, startAngle, endAngle, counterclockwise = false) {
this.#ctx.beginPath() this._ctx.beginPath()
this.#ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise) this._ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise)
this.#ctx.stroke() this._ctx.stroke()
} }
/** /**
@ -585,9 +572,9 @@ class CanvasAPI extends Module {
* @param {number} radius * @param {number} radius
*/ */
disc(x, y, radius) { disc(x, y, radius) {
this.#ctx.beginPath() this._ctx.beginPath()
this.#ctx.arc(x, y, radius, 0, 2 * Math.PI) this._ctx.arc(x, y, radius, 0, 2 * Math.PI)
this.#ctx.fill() this._ctx.fill()
} }
/** /**
@ -598,7 +585,7 @@ class CanvasAPI extends Module {
* @param {number} h * @param {number} h
*/ */
fillRect(x, y, w, h) { fillRect(x, y, w, h) {
this.#ctx.fillRect(x, y, w, h) this._ctx.fillRect(x, y, w, h)
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -17,7 +17,6 @@
*/ */
import { Interface } from "./interface.mjs" import { Interface } from "./interface.mjs"
import { BaseEventEmitter } from "../events.mjs"
// Define Modules interface before they are imported. // Define Modules interface before they are imported.
globalThis.Modules = globalThis.Modules || {} globalThis.Modules = globalThis.Modules || {}
@ -25,13 +24,7 @@ globalThis.Modules = globalThis.Modules || {}
/** /**
* Base class for global APIs in runtime. * Base class for global APIs in runtime.
*/ */
export class Module extends BaseEventEmitter { export class Module {
/** @type {string} */
#name
/** @type {Object.<string, (Interface|string|number|boolean)>} */
#initializationParameters
/** @type {boolean} */
#initialized = false
/** /**
* *
@ -39,18 +32,11 @@ export class Module extends BaseEventEmitter {
* @param {Object.<string, (Interface|string|number|boolean)>} initializationParameters - List of parameters for the initialize function. * @param {Object.<string, (Interface|string|number|boolean)>} initializationParameters - List of parameters for the initialize function.
*/ */
constructor(name, initializationParameters = {}) { constructor(name, initializationParameters = {}) {
super()
console.log(`Loading module ${name}...`) console.log(`Loading module ${name}...`)
this.#name = name this.__name = name
this.#initializationParameters = initializationParameters 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 * @param {Object.<string, any>} options
*/ */
initialize(options) { initialize(options) {
if(this.#initialized) if(this.initialized)
throw new Error(`Cannot reinitialize module ${this.#name}.`) throw new Error(`Cannot reinitialize module ${this.__name}.`)
console.log(`Initializing ${this.#name}...`) console.log(`Initializing ${this.__name}...`)
for(const [name, value] of Object.entries(this.#initializationParameters)) { for(const [name, value] of Object.entries(this.__initializationParameters)) {
if(!options.hasOwnProperty(name)) 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) 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]) 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
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -19,7 +19,7 @@
import { Module } from "./common.mjs" import { Module } from "./common.mjs"
import { Parser } from "../lib/expr-eval/parser.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 // Variables not provided by expr-eval.js, needs to be provided manually
"pi": Math.PI, "pi": Math.PI,
"PI": Math.PI, "PI": Math.PI,
@ -35,17 +35,15 @@ const EVAL_VARIABLES = {
} }
class ExprParserAPI extends Module { class ExprParserAPI extends Module {
#parser = new Parser()
constructor() { constructor() {
super("ExprParser") super("ExprParser")
this.currentVars = {} 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.integral = this.integral.bind(this)
this.#parser.functions.derivative = this.derivative.bind(this) this._parser.functions.derivative = this.derivative.bind(this)
} }
/** /**
@ -70,7 +68,7 @@ class ExprParserAPI extends Module {
[f, variable] = args [f, variable] = args
if(typeof f !== "string" || typeof variable !== "string") if(typeof f !== "string" || typeof variable !== "string")
throw EvalError(qsTranslate("usage", "Usage:\n%1").arg(usage2)) 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 } else
throw EvalError(qsTranslate("usage", "Usage:\n%1\n%2").arg(usage1).arg(usage2)) throw EvalError(qsTranslate("usage", "Usage:\n%1\n%2").arg(usage1).arg(usage2))
return f return f
@ -81,14 +79,14 @@ class ExprParserAPI extends Module {
* @returns {ExprEvalExpression} * @returns {ExprEvalExpression}
*/ */
parse(expression) { 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 usage1 = qsTranslate("usage", "integral(<from: number>, <to: number>, <f: ExecutableObject>)")
let usage2 = qsTranslate("usage", "integral(<from: number>, <to: number>, <f: string>, <variable: string>)") let usage2 = qsTranslate("usage", "integral(<from: number>, <to: number>, <f: string>, <variable: string>)")
let f = this.parseArgumentsForFunction(args, usage1, usage2) 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)) throw EvalError(qsTranslate("usage", "Usage:\n%1\n%2").arg(usage1).arg(usage2))
// https://en.wikipedia.org/wiki/Simpson%27s_rule // 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 usage2 = qsTranslate("usage", "derivative(<f: string>, <variable: string>, <x: number>)")
let x = args.pop() let x = args.pop()
let f = this.parseArgumentsForFunction(args, usage1, usage2) 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)) 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 return (f(x + derivative_precision / 2) - f(x - derivative_precision / 2)) / derivative_precision
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -17,164 +17,60 @@
*/ */
import { Module } from "./common.mjs" import { Module } from "./common.mjs"
import { HelperInterface, NUMBER, STRING } from "./interface.mjs" import { HistoryInterface, NUMBER, STRING } from "./interface.mjs"
import { BaseEvent } from "../events.mjs"
import { Action, Actions } from "../history/index.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 { class HistoryAPI extends Module {
static emits = ["cleared", "loaded", "added", "undone", "redone"]
#helper
constructor() { constructor() {
super("History", { super("History", {
helper: HelperInterface, historyObj: HistoryInterface,
themeTextColor: STRING, themeTextColor: STRING,
imageDepth: NUMBER, imageDepth: NUMBER,
fontSize: NUMBER fontSize: NUMBER
}) })
// History QML object // History QML object
/** @type {Action[]} */ this.history = null
this.undoStack = []
/** @type {Action[]} */
this.redoStack = []
this.themeTextColor = "#FF0000" this.themeTextColor = "#FF0000"
this.imageDepth = 2 this.imageDepth = 2
this.fontSize = 28 this.fontSize = 28
} }
/** initialize({ historyObj, themeTextColor, imageDepth, fontSize }) {
* @param {HelperInterface} historyObj super.initialize({ historyObj, themeTextColor, imageDepth, fontSize })
* @param {string} themeTextColor this.history = historyObj
* @param {number} imageDepth
* @param {number} fontSize
*/
initialize({ helper, themeTextColor, imageDepth, fontSize }) {
super.initialize({ helper, themeTextColor, imageDepth, fontSize })
this.#helper = helper
this.themeTextColor = themeTextColor this.themeTextColor = themeTextColor
this.imageDepth = imageDepth this.imageDepth = imageDepth
this.fontSize = fontSize this.fontSize = fontSize
} }
/**
* Undoes the Action at the top of the undo stack and pushes it to the top of the redo stack.
*/
undo() { undo() {
if(!this.initialized) throw new Error("Attempting undo before initialize!") if(!this.initialized) throw new Error("Attempting undo before initialize!")
if(this.undoStack.length > 0) { this.history.undo()
const action = this.undoStack.pop()
action.undo()
this.redoStack.push(action)
this.emit(new UndoneEvent(action))
}
} }
/**
* Redoes the Action at the top of the redo stack and pushes it to the top of the undo stack.
*/
redo() { redo() {
if(!this.initialized) throw new Error("Attempting redo before initialize!") if(!this.initialized) throw new Error("Attempting redo before initialize!")
if(this.redoStack.length > 0) { this.history.redo()
const action = this.redoStack.pop()
action.redo()
this.undoStack.push(action)
this.emit(new RedoneEvent(action))
}
} }
/**
* Clears both undo and redo stacks completely.
*/
clear() { clear() {
if(!this.initialized) throw new Error("Attempting clear before initialize!") if(!this.initialized) throw new Error("Attempting clear before initialize!")
this.undoStack = [] this.history.clear()
this.redoStack = []
this.emit(new ClearedEvent())
} }
/**
* Adds an instance of HistoryLib.Action to history.
* @param action
*/
addToHistory(action) { addToHistory(action) {
if(!this.initialized) throw new Error("Attempting addToHistory before initialize!") if(!this.initialized) throw new Error("Attempting addToHistory before initialize!")
if(action instanceof Action) { this.history.addToHistory(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))
}
} }
/** unserialize(...data) {
* Unserializes both the undo stack and redo stack from serialized content.
* @param {[string, any[]][]} undoSt
* @param {[string, any[]][]} redoSt
*/
unserialize(undoSt, redoSt) {
if(!this.initialized) throw new Error("Attempting unserialize before initialize!") if(!this.initialized) throw new Error("Attempting unserialize before initialize!")
this.clear() this.history.unserialize(...data)
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())
} }
/**
* Serializes history into JSON-able content.
* @return {[[string, any[]], [string, any[]]]}
*/
serialize() { serialize() {
if(!this.initialized) throw new Error("Attempting serialize before initialize!") if(!this.initialized) throw new Error("Attempting serialize before initialize!")
let undoSt = [], redoSt = []; return this.history.serialize()
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]
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -17,7 +17,6 @@
*/ */
import Objects from "./objects.mjs" import Objects from "./objects.mjs"
import Settings from "./settings.mjs"
import ExprParser from "./expreval.mjs" import ExprParser from "./expreval.mjs"
import Latex from "./latex.mjs" import Latex from "./latex.mjs"
import History from "./history.mjs" import History from "./history.mjs"
@ -27,7 +26,6 @@ import Preferences from "./preferences.mjs"
export default { export default {
Objects, Objects,
Settings,
ExprParser, ExprParser,
Latex, Latex,
History, History,

View file

@ -1,9 +1,9 @@
/** /*!
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* *
* @author Ad5001 <mail@ad5001.eu> * @author Ad5001 <mail@ad5001.eu>
* @license GPL-3.0-or-later * @license GPL-3.0-or-later
* @copyright (C) 2021-2025 Ad5001 * @copyright (C) 2021-2024 Ad5001
* @preserve * @preserve
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@ -35,8 +35,9 @@ export class Interface {
* Throws an error if the implementation does not conform to the interface. * Throws an error if the implementation does not conform to the interface.
* @param {typeof Interface} interface_ * @param {typeof Interface} interface_
* @param {object} classToCheck * @param {object} classToCheck
* @return {boolean}
*/ */
static checkImplementation(interface_, classToCheck) { static check_implementation(interface_, classToCheck) {
const properties = new interface_() const properties = new interface_()
const interfaceName = interface_.name const interfaceName = interface_.name
const toCheckName = classToCheck.constructor.name const toCheckName = classToCheck.constructor.name
@ -51,7 +52,7 @@ export class Interface {
else if((typeof value) === "object") else if((typeof value) === "object")
// Test type of object. // Test type of object.
if(value instanceof Interface) if(value instanceof Interface)
Interface.checkImplementation(value, classToCheck[property]) Interface.check_implementation(value, classToCheck[property])
else if(value.prototype && !(classToCheck[property] instanceof value)) else if(value.prototype && !(classToCheck[property] instanceof value))
throw new Error(`Property '${property}' of ${interfaceName} implementation ${toCheckName} is not '${value.constructor.name}'.`) 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} */ /** @type {function(string): CanvasRenderingContext2D} */
getContext = FUNCTION getContext = FUNCTION
/** @type {function(rect)} */ /** @type {function(rect)} */
markDirty = FUNCTION markDirty = FUNCTION
/** @type {function(string): Promise} */ /** @type {function(string)} */
loadImageAsync = FUNCTION loadImage = FUNCTION
/** @type {function(string)} */ /** @type {function(string)} */
isImageLoading = FUNCTION isImageLoading = FUNCTION
/** @type {function(string)} */ /** @type {function(string)} */
@ -77,28 +97,30 @@ export class CanvasInterface extends Interface {
export class RootInterface extends Interface { export class RootInterface extends Interface {
width = NUMBER width = NUMBER
height = NUMBER height = NUMBER
updateObjectsLists = FUNCTION
} }
export class DialogInterface extends Interface { export class DialogInterface extends Interface {
show = FUNCTION show = FUNCTION
} }
export class HistoryInterface extends Interface {
undo = FUNCTION
redo = FUNCTION
clear = FUNCTION
addToHistory = FUNCTION
unserialize = FUNCTION
serialize = FUNCTION
}
export class LatexInterface extends Interface { export class LatexInterface extends Interface {
supportsAsyncRender = BOOLEAN
/** /**
* @param {string} markup - LaTeX markup to render * @param {string} markup - LaTeX markup to render
* @param {number} fontSize - Font size (in pt) to render * @param {number} fontSize - Font size (in pt) to render
* @param {string} color - Color of the text to render * @param {string} color - Color of the text to render
* @returns {string} - Comma separated data of the image (source, width, height) * @returns {string} - Comma separated data of the image (source, width, height)
*/ */
renderSync = FUNCTION render = 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
/** /**
* @param {string} markup - LaTeX markup to render * @param {string} markup - LaTeX markup to render
* @param {number} fontSize - Font size (in pt) 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 * Gets a setting from the config
* @param {string} settingName - Setting (and its dot-separated namespace) to get (e.g. "default_graph.xmin") * @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 getSetting = FUNCTION
/** /**
* Sets a setting in the config * Sets a setting in the config
* @param {string} settingName - Setting (and its dot-separated namespace) to set (e.g. "default_graph.xmin") * @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 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 * @returns {string} the loaded data - just JSON encoded, requires the "LPFv1" mime to be stripped
*/ */
load = FUNCTION load = FUNCTION
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -19,69 +19,36 @@
import { Module } from "./common.mjs" import { Module } from "./common.mjs"
import Objects from "./objects.mjs" import Objects from "./objects.mjs"
import History from "./history.mjs" import History from "./history.mjs"
import Settings from "./settings.mjs" import Canvas from "./canvas.mjs"
import { DialogInterface, RootInterface } from "./interface.mjs" import { DialogInterface, RootInterface, SettingsInterface } from "./interface.mjs"
import { BaseEvent } from "../events.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 { class IOAPI extends Module {
static emits = ["loaded", "saved", "modified"]
/** @type {RootInterface} */
#rootElement
/** @type {{show: function(string)}} */
#alert
#saved = true
constructor() { constructor() {
super("IO", { super("IO", {
alert: DialogInterface, alert: DialogInterface,
root: RootInterface root: RootInterface,
settings: SettingsInterface
}) })
/**
// Settings.on("changed", this.__emitModified.bind(this)) * Path of the currently opened file. Empty if no file is opened.
History.on("added undone redone", this.__emitModified.bind(this)) * @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. * Initializes module with QML elements.
* @param {RootInterface} root * @param {RootInterface} root
* @param {SettingsInterface} settings
* @param {{show: function(string)}} alert * @param {{show: function(string)}} alert
*/ */
initialize({ root, alert }) { initialize({ root, settings, alert }) {
super.initialize({ root, alert }) super.initialize({ root, settings, alert })
this.#rootElement = root this.rootElement = root
this.#alert = alert this.settings = settings
this.alert = alert
} }
/** /**
@ -93,7 +60,7 @@ class IOAPI extends Module {
// Add extension if necessary // Add extension if necessary
if(["lpf"].indexOf(filename.split(".")[filename.split(".").length - 1]) === -1) if(["lpf"].indexOf(filename.split(".")[filename.split(".").length - 1]) === -1)
filename += ".lpf" filename += ".lpf"
Settings.set("saveFilename", filename, false) this.saveFilename = filename
let objs = {} let objs = {}
for(let objType in Objects.currentObjects) { for(let objType in Objects.currentObjects) {
objs[objType] = [] objs[objType] = []
@ -102,29 +69,28 @@ class IOAPI extends Module {
} }
} }
let settings = { let settings = {
"xzoom": Settings.xzoom, "xzoom": this.settings.xzoom,
"yzoom": Settings.yzoom, "yzoom": this.settings.yzoom,
"xmin": Settings.xmin, "xmin": this.settings.xmin,
"ymax": Settings.ymax, "ymax": this.settings.ymax,
"xaxisstep": Settings.xaxisstep, "xaxisstep": this.settings.xaxisstep,
"yaxisstep": Settings.yaxisstep, "yaxisstep": this.settings.yaxisstep,
"xaxislabel": Settings.xlabel, "xaxislabel": this.settings.xlabel,
"yaxislabel": Settings.ylabel, "yaxislabel": this.settings.ylabel,
"logscalex": Settings.logscalex, "logscalex": this.settings.logscalex,
"linewidth": Settings.linewidth, "linewidth": this.settings.linewidth,
"showxgrad": Settings.showxgrad, "showxgrad": this.settings.showxgrad,
"showygrad": Settings.showygrad, "showygrad": this.settings.showygrad,
"textsize": Settings.textsize, "textsize": this.settings.textsize,
"history": History.serialize(), "history": History.serialize(),
"width": this.#rootElement.width, "width": this.rootElement.width,
"height": this.#rootElement.height, "height": this.rootElement.height,
"objects": objs, "objects": objs,
"type": "logplotv1" "type": "logplotv1"
} }
Helper.write(filename, JSON.stringify(settings)) Helper.write(filename, JSON.stringify(settings))
this.#alert.show(qsTranslate("io", "Saved plot to '%1'.").arg(filename.split("/").pop())) this.alert.show(qsTranslate("io", "Saved plot to '%1'.").arg(filename.split("/").pop()))
this.#saved = true History.history.saved = true
this.emit(new SavedEvent())
} }
/** /**
@ -135,32 +101,32 @@ class IOAPI extends Module {
if(!this.initialized) throw new Error("Attempting loadDiagram before initialize!") if(!this.initialized) throw new Error("Attempting loadDiagram before initialize!")
if(!History.initialized) throw new Error("Attempting loadDiagram before history is initialized!") if(!History.initialized) throw new Error("Attempting loadDiagram before history is initialized!")
let basename = filename.split("/").pop() 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 data = JSON.parse(Helper.load(filename))
let error = "" let error = ""
if(data.hasOwnProperty("type") && data["type"] === "logplotv1") { if(data.hasOwnProperty("type") && data["type"] === "logplotv1") {
History.clear() History.clear()
// Importing settings // Importing settings
Settings.set("saveFilename", filename, false) this.settings.saveFilename = filename
Settings.set("xzoom", parseFloat(data["xzoom"]) || 100, false) this.settings.xzoom = parseFloat(data["xzoom"]) || 100
Settings.set("yzoom", parseFloat(data["yzoom"]) || 10, false) this.settings.yzoom = parseFloat(data["yzoom"]) || 10
Settings.set("xmin", parseFloat(data["xmin"]) || 5 / 10, false) this.settings.xmin = parseFloat(data["xmin"]) || 5 / 10
Settings.set("ymax", parseFloat(data["ymax"]) || 24, false) this.settings.ymax = parseFloat(data["ymax"]) || 24
Settings.set("xaxisstep", data["xaxisstep"] || "4", false) this.settings.xaxisstep = data["xaxisstep"] || "4"
Settings.set("yaxisstep", data["yaxisstep"] || "4", false) this.settings.yaxisstep = data["yaxisstep"] || "4"
Settings.set("xlabel", data["xaxislabel"] || "", false) this.settings.xlabel = data["xaxislabel"] || ""
Settings.set("ylabel", data["yaxislabel"] || "", false) this.settings.ylabel = data["yaxislabel"] || ""
Settings.set("logscalex", data["logscalex"] === true, false) this.settings.logscalex = data["logscalex"] === true
if("showxgrad" in data) if("showxgrad" in data)
Settings.set("showxgrad", data["showxgrad"], false) this.settings.showxgrad = data["showxgrad"]
if("showygrad" in data) if("showygrad" in data)
Settings.set("showygrad", data["showygrad"], false) this.settings.textsize = data["showygrad"]
if("linewidth" in data) if("linewidth" in data)
Settings.set("linewidth", data["linewidth"], false) this.settings.linewidth = data["linewidth"]
if("textsize" in data) if("textsize" in data)
Settings.set("textsize", data["textsize"], false) this.settings.textsize = data["textsize"]
this.#rootElement.height = parseFloat(data["height"]) || 500 this.rootElement.height = parseFloat(data["height"]) || 500
this.#rootElement.width = parseFloat(data["width"]) || 1000 this.rootElement.width = parseFloat(data["width"]) || 1000
// Importing objects // Importing objects
Objects.currentObjects = {} Objects.currentObjects = {}
@ -191,18 +157,20 @@ class IOAPI extends Module {
if("history" in data) if("history" in data)
History.unserialize(...data["history"]) History.unserialize(...data["history"])
// Refreshing sidebar
this.rootElement.updateObjectsLists()
} else { } else {
error = qsTranslate("io", "Invalid file provided.") error = qsTranslate("io", "Invalid file provided.")
} }
if(error !== "") { if(error !== "") {
console.log(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 // TODO: Error handling
return return
} }
this.#alert.show(qsTranslate("io", "Loaded file '%1'.").arg(basename)) Canvas.redraw()
this.#saved = true this.alert.show(qsTranslate("io", "Loaded file '%1'.").arg(basename))
this.emit(new LoadedEvent()) History.history.saved = true
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -17,12 +17,11 @@
*/ */
import { Module } from "./common.mjs" import { Module } from "./common.mjs"
import { BaseEvent } from "../events.mjs"
import * as Instruction from "../lib/expr-eval/instruction.mjs" import * as Instruction from "../lib/expr-eval/instruction.mjs"
import { escapeValue } from "../lib/expr-eval/expression.mjs" import { escapeValue } from "../lib/expr-eval/expression.mjs"
import { HelperInterface, LatexInterface } from "./interface.mjs" import { HelperInterface, LatexInterface } from "./interface.mjs"
const unicodechars = ["pi", "∞", const unicodechars = [
"α", "β", "γ", "δ", "ε", "ζ", "η", "α", "β", "γ", "δ", "ε", "ζ", "η",
"π", "θ", "κ", "λ", "μ", "ξ", "ρ", "π", "θ", "κ", "λ", "μ", "ξ", "ρ",
"ς", "σ", "τ", "φ", "χ", "ψ", "ω", "ς", "σ", "τ", "φ", "χ", "ψ", "ω",
@ -31,9 +30,9 @@ const unicodechars = ["pi", "∞",
"ₕ", "ₖ", "ₗ", "ₘ", "ₙ", "ₚ", "ₛ", "ₕ", "ₖ", "ₗ", "ₘ", "ₙ", "ₚ", "ₛ",
"ₜ", "¹", "²", "³", "⁴", "⁵", "⁶", "ₜ", "¹", "²", "³", "⁴", "⁵", "⁶",
"⁷", "⁸", "⁹", "⁰", "₁", "₂", "₃", "⁷", "⁸", "⁹", "⁰", "₁", "₂", "₃",
"₄", "₅", "₆", "₇", "₈", "₉", "₀" "₄", "₅", "₆", "₇", "₈", "₉", "₀",
] "pi", "∞"]
const equivalchars = ["\\pi", "\\infty", const equivalchars = [
"\\alpha", "\\beta", "\\gamma", "\\delta", "\\epsilon", "\\zeta", "\\eta", "\\alpha", "\\beta", "\\gamma", "\\delta", "\\epsilon", "\\zeta", "\\eta",
"\\pi", "\\theta", "\\kappa", "\\lambda", "\\mu", "\\xi", "\\rho", "\\pi", "\\theta", "\\kappa", "\\lambda", "\\mu", "\\xi", "\\rho",
"\\sigma", "\\sigma", "\\tau", "\\phi", "\\chi", "\\psi", "\\omega", "\\sigma", "\\sigma", "\\tau", "\\phi", "\\chi", "\\psi", "\\omega",
@ -43,29 +42,7 @@ const equivalchars = ["\\pi", "\\infty",
"{}_{t}", "{}^{1}", "{}^{2}", "{}^{3}", "{}^{4}", "{}^{5}", "{}^{6}", "{}_{t}", "{}^{1}", "{}^{2}", "{}^{3}", "{}^{4}", "{}^{5}", "{}^{6}",
"{}^{7}", "{}^{8}", "{}^{9}", "{}^{0}", "{}_{1}", "{}_{2}", "{}_{3}", "{}^{7}", "{}^{8}", "{}^{9}", "{}^{0}", "{}_{1}", "{}_{2}", "{}_{3}",
"{}_{4}", "{}_{5}", "{}_{6}", "{}_{7}", "{}_{8}", "{}_{9}", "{}_{0}", "{}_{4}", "{}_{5}", "{}_{6}", "{}_{7}", "{}_{8}", "{}_{9}", "{}_{0}",
] "\\pi", "\\infty"]
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
}
}
/** /**
* Class containing the result of a LaTeX render. * Class containing the result of a LaTeX render.
@ -83,11 +60,6 @@ class LatexRenderResult {
} }
class LatexAPI extends Module { class LatexAPI extends Module {
static emits = ["async-render-started", "async-render-finished"]
/** @type {LatexInterface} */
#latex = null
constructor() { constructor() {
super("Latex", { super("Latex", {
latex: LatexInterface, latex: LatexInterface,
@ -97,7 +69,6 @@ class LatexAPI extends Module {
* true if latex has been enabled by the user, false otherwise. * true if latex has been enabled by the user, false otherwise.
*/ */
this.enabled = false this.enabled = false
this.promises = new Set()
} }
/** /**
@ -106,8 +77,9 @@ class LatexAPI extends Module {
*/ */
initialize({ latex, helper }) { initialize({ latex, helper }) {
super.initialize({ latex, helper }) super.initialize({ latex, helper })
this.#latex = latex this.latex = latex
this.enabled = helper.getSetting("enable_latex") this.helper = helper
this.enabled = helper.getSettingBool("enable_latex")
} }
/** /**
@ -121,7 +93,7 @@ class LatexAPI extends Module {
*/ */
findPrerendered(markup, fontSize, color) { findPrerendered(markup, fontSize, color) {
if(!this.initialized) throw new Error("Attempting findPrerendered before initialize!") 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 let ret = null
if(data !== "") if(data !== "")
ret = new LatexRenderResult(...data.split(",")) ret = new LatexRenderResult(...data.split(","))
@ -138,19 +110,7 @@ class LatexAPI extends Module {
*/ */
async requestAsyncRender(markup, fontSize, color) { async requestAsyncRender(markup, fontSize, color) {
if(!this.initialized) throw new Error("Attempting requestAsyncRender before initialize!") if(!this.initialized) throw new Error("Attempting requestAsyncRender before initialize!")
let render let args = this.latex.render(markup, fontSize, color).split(",")
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(",")
return new LatexRenderResult(...args) return new LatexRenderResult(...args)
} }
@ -175,10 +135,9 @@ class LatexAPI extends Module {
*/ */
parif(elem, contents) { parif(elem, contents) {
elem = elem.toString() elem = elem.toString()
const contains = contents.some(x => elem.indexOf(x) > 0) if(elem[0] !== "(" && elem[elem.length - 1] !== ")" && contents.some(x => elem.indexOf(x) > 0))
if(contains && (elem[0] !== "(" || elem.at(-1) !== ")"))
return this.par(elem) return this.par(elem)
if(!contains && elem[0] === "(" && elem.at(-1) === ")") if(elem[0] === "(" && elem[elem.length - 1] === ")")
return elem.removeEnclosure() return elem.removeEnclosure()
return elem return elem
} }
@ -196,21 +155,20 @@ class LatexAPI extends Module {
if(args.length === 3) if(args.length === 3)
return `\\frac{d${args[0].removeEnclosure().replaceAll(args[1].removeEnclosure(), "x")}}{dx}` return `\\frac{d${args[0].removeEnclosure().replaceAll(args[1].removeEnclosure(), "x")}}{dx}`
else else
return `\\frac{d${args[0]}}{dx}(${args[1]})` return `\\frac{d${args[0]}}{dx}(x)`
case "integral": case "integral":
if(args.length === 4) if(args.length === 4)
return `\\int\\limits_{${args[0]}}^{${args[1]}}${args[2].removeEnclosure()} d${args[3].removeEnclosure()}` return `\\int\\limits_{${args[0]}}^{${args[1]}}${args[2].removeEnclosure()} d${args[3].removeEnclosure()}`
else else
return `\\int\\limits_{${args[0]}}^{${args[1]}}${args[2]}(t) dt` return `\\int\\limits_{${args[0]}}^{${args[1]}}${args[2]}(t) dt`
case "sqrt": case "sqrt":
const arg = this.parif(args.join(", "), []) return `\\sqrt\\left(${args.join(", ")}\\right)`
return `\\sqrt{${arg}}`
case "abs": case "abs":
return `\\left|${args.join(", ")}\\right|` return `\\left|${args.join(", ")}\\right|`
case "floor": case "floor":
return `\\left\\lfloor{${args.join(", ")}}\\right\\rfloor` return `\\left\\lfloor${args.join(", ")}\\right\\rfloor`
case "ceil": case "ceil":
return `\\left\\lceil{${args.join(", ")}}\\right\\rceil` return `\\left\\lceil${args.join(", ")}\\right\\rceil`
default: default:
return `\\mathrm{${f}}\\left(${args.join(", ")}\\right)` return `\\mathrm{${f}}\\left(${args.join(", ")}\\right)`
} }
@ -224,17 +182,16 @@ class LatexAPI extends Module {
* @returns {string} * @returns {string}
*/ */
variable(vari, wrapIn$ = false) { variable(vari, wrapIn$ = false) {
if(wrapIn$) { if(wrapIn$)
for(let i = 0; i < unicodechars.length; i++) { for(let i = 0; i < unicodechars.length; i++) {
if(vari.includes(unicodechars[i])) if(vari.includes(unicodechars[i]))
vari = vari.replaceAll(unicodechars[i], "$" + equivalchars[i] + "$") vari = vari.replaceAll(unicodechars[i], "$" + equivalchars[i] + "$")
} }
} else { else
for(let i = 0; i < unicodechars.length; i++) { for(let i = 0; i < unicodechars.length; i++) {
if(vari.includes(unicodechars[i])) if(vari.includes(unicodechars[i]))
vari = vari.replaceAll(unicodechars[i], equivalchars[i]) vari = vari.replaceAll(unicodechars[i], equivalchars[i])
} }
}
return vari return vari
} }
@ -302,7 +259,7 @@ class LatexAPI extends Module {
throw new EvalError("Unknown operator " + item.value + ".") throw new EvalError("Unknown operator " + item.value + ".")
} }
break break
case Instruction.IOP3: // Ternary operator case Instruction.IOP3: // Thirdiary operator
n3 = nstack.pop() n3 = nstack.pop()
n2 = nstack.pop() n2 = nstack.pop()
n1 = nstack.pop() n1 = nstack.pop()
@ -329,7 +286,7 @@ class LatexAPI extends Module {
nstack.push(this.parif(n1, ["+", "-", "*", "/", "^"]) + "!") nstack.push(this.parif(n1, ["+", "-", "*", "/", "^"]) + "!")
break break
default: default:
nstack.push(this.functionToLatex(f, [this.parif(n1, ["+", "-", "*", "/", "^"])])) nstack.push(f + this.parif(n1, ["+", "-", "*", "/", "^"]))
break break
} }
break break
@ -364,6 +321,9 @@ class LatexAPI extends Module {
throw new EvalError("invalid Expression") throw new EvalError("invalid Expression")
} }
} }
if(nstack.length > 1) {
nstack = [nstack.join(";")]
}
return String(nstack[0]) return String(nstack[0])
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -17,17 +17,13 @@
*/ */
import { Module } from "./common.mjs" import { Module } from "./common.mjs"
import { textsub } from "../utils/index.mjs" import { textsub } from "../utils.mjs"
class ObjectsAPI extends Module { class ObjectsAPI extends Module {
constructor() { constructor() {
super("Objects") super("Objects")
/**
* List of object constructors.
* @type {Object.<string,typeof DrawableObject>}
*/
this.types = {} this.types = {}
/** /**
* List of objects for each type of object. * 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. * @param {string} newName - Name to rename the object to.
*/ */
renameObject(oldName, newName) { renameObject(oldName, newName) {
const obj = this.currentObjectsByName[oldName] let obj = this.currentObjectsByName[oldName]
delete this.currentObjectsByName[oldName] delete this.currentObjectsByName[oldName]
this.currentObjectsByName[newName] = obj this.currentObjectsByName[newName] = obj
obj.name = newName obj.name = newName
@ -80,7 +76,7 @@ class ObjectsAPI extends Module {
* @param {string} objName - Current name of the object. * @param {string} objName - Current name of the object.
*/ */
deleteObject(objName) { deleteObject(objName) {
const obj = this.currentObjectsByName[objName] let obj = this.currentObjectsByName[objName]
if(obj !== undefined) { if(obj !== undefined) {
this.currentObjects[obj.type].splice(this.currentObjects[obj.type].indexOf(obj), 1) this.currentObjects[obj.type].splice(this.currentObjects[obj.type].indexOf(obj), 1)
obj.delete() obj.delete()

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -20,9 +20,6 @@ import General from "../preferences/general.mjs"
import Editor from "../preferences/expression.mjs" import Editor from "../preferences/expression.mjs"
import DefaultGraph from "../preferences/default.mjs" import DefaultGraph from "../preferences/default.mjs"
/**
* Module for application wide settings.
*/
class PreferencesAPI extends Module { class PreferencesAPI extends Module {
constructor() { constructor() {
super("Preferences") super("Preferences")

View file

@ -1,186 +0,0 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 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

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -63,7 +63,7 @@ export default class BodePhase extends ExecutableObject {
// Create new point // Create new point
om_0 = Objects.createNewRegisteredObject("Point", [Objects.getNewName("ω"), this.color, "name"]) om_0 = Objects.createNewRegisteredObject("Point", [Objects.getNewName("ω"), this.color, "name"])
om_0.labelPosition = this.phase.execute() >= 0 ? "above" : "below" 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" labelPosition = "below"
} }
om_0.requiredBy.push(this) om_0.requiredBy.push(this)

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -16,9 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { getRandomColor } from "../utils.mjs"
import Objects from "../module/objects.mjs" import Objects from "../module/objects.mjs"
import Latex from "../module/latex.mjs" import Latex from "../module/latex.mjs"
import { getRandomColor } from "../utils/index.mjs"
import { ensureTypeSafety, serializesByPropertyType } from "../parameters.mjs" import { ensureTypeSafety, serializesByPropertyType } from "../parameters.mjs"
// This file contains the default data to be imported from all other objects // 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) currentObjectsByName[objName].requiredBy.push(this)
} }
} }
if(this[property].canBeCached && this[property].requiredObjects().length > 0) if(this[property].cached && this[property].requiredObjects().length > 0)
// Recalculate // Recalculate
this[property].recache() this[property].recache()

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

Some files were not shown because too many files have changed in this diff Show more