Compare commits
No commits in common. "master" and "rollup-js" have entirely different histories.
6
.gitignore
vendored
|
@ -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*
|
||||||
|
|
18
README.md
|
@ -1,4 +1,4 @@
|
||||||
# ![icon](https://apps.ad5001.eu/icons/apps/svg/logarithmplotter.svg) LogarithmPlotter
|
# ![icon](https://git.ad5001.eu/Ad5001/LogarithmPlotter/raw/branch/master/logplotter.svg) LogarithmPlotter
|
||||||
|
|
||||||
[![Build Status](https://ci.ad5001.eu/api/badges/Ad5001/LogarithmPlotter/status.svg)](https://ci.ad5001.eu/Ad5001/LogarithmPlotter)
|
[![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,13 +68,7 @@ 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
|
||||||
|
@ -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
|
||||||
<r@undefined.ch>, ported to javascript by Matthew Crumley
|
<r@undefined.ch>, ported to javascript by Matthew Crumley
|
||||||
<email@matthewcrumley.com> (http://silentmatt.com/), and then to QMLJS by Ad5001.
|
<email@matthewcrumley.com> (http://silentmatt.com/), and then to QMLJS by Ad5001.
|
||||||
|
|
||||||
All files in (common/src/lib/expr-eval/) except integration.mjs are licensed
|
All files in (LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/) except integration.mjs are licensed
|
||||||
under the [MIT License](https://raw.githubusercontent.com/silentmatt/expr-eval/master/LICENSE.txt).
|
under the [MIT License](https://raw.githubusercontent.com/silentmatt/expr-eval/master/LICENSE.txt).
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
pyside6-lrelease *.ts
|
lrelease *.ts
|
||||||
|
|
|
@ -21,7 +21,7 @@ replace() {
|
||||||
|
|
||||||
rm ../qml/eu/ad5001/LogarithmPlotter/js/index.mjs # Remove index which should not be scanned
|
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
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
../common/appearance.svg
|
|
|
@ -1 +0,0 @@
|
||||||
../common/appearance.svg
|
|
|
@ -1 +0,0 @@
|
||||||
../common/arrow.svg
|
|
|
@ -1 +0,0 @@
|
||||||
../common/position.svg
|
|
|
@ -1 +0,0 @@
|
||||||
../common/angle.svg
|
|
|
@ -1 +0,0 @@
|
||||||
../common/angle.svg
|
|
|
@ -1 +0,0 @@
|
||||||
../common/appearance.svg
|
|
|
@ -1 +0,0 @@
|
||||||
../common/target.svg
|
|
|
@ -1 +0,0 @@
|
||||||
../common/position.svg
|
|
|
@ -1 +0,0 @@
|
||||||
../common/label.svg
|
|
|
@ -1 +0,0 @@
|
||||||
../common/angle.svg
|
|
|
@ -1 +0,0 @@
|
||||||
../common/position.svg
|
|
|
@ -1 +0,0 @@
|
||||||
../common/position.svg
|
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
1
assets/icons/settings/custom/Display Mode.svg
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../common/appearance.svg
|
1
assets/icons/settings/custom/Display Style.svg
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../common/appearance.svg
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
1
assets/icons/settings/custom/Label Position.svg
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../common/arrow.svg
|
1
assets/icons/settings/custom/Label X.svg
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../common/position.svg
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
1
assets/icons/settings/custom/Phase.svg
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../common/angle.svg
|
1
assets/icons/settings/custom/Point Style.svg
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../common/appearance.svg
|
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 2 KiB |
1
assets/icons/settings/custom/Target Element.svg
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../common/target.svg
|
1
assets/icons/settings/custom/Target Value Position.svg
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../common/position.svg
|
1
assets/icons/settings/custom/Text.svg
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../common/label.svg
|
1
assets/icons/settings/custom/Unit.svg
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../common/angle.svg
|
1
assets/icons/settings/custom/X.svg
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../common/position.svg
|
1
assets/icons/settings/custom/Y.svg
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../common/position.svg
|
1
assets/icons/settings/custom/ω_0.svg
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../common/angle.svg
|
|
@ -1,171 +1,64 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?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 <mail@ad5001.eu></dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:creator>
|
|
||||||
<dc:rights>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>(c) Adsooi 2021-2024</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:rights>
|
|
||||||
<cc:license
|
|
||||||
rdf:resource="http://creativecommons.org/licenses/by-nc-sa/4.0/" />
|
|
||||||
</cc:Work>
|
|
||||||
<cc:License
|
|
||||||
rdf:about="http://creativecommons.org/licenses/by-nc-sa/4.0/">
|
|
||||||
<cc:permits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Reproduction" />
|
|
||||||
<cc:permits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Distribution" />
|
|
||||||
<cc:requires
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Notice" />
|
|
||||||
<cc:requires
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Attribution" />
|
|
||||||
<cc:prohibits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
|
|
||||||
<cc:permits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
|
|
||||||
<cc:requires
|
|
||||||
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
|
|
||||||
</cc:License>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
</svg>
|
|
||||||
|
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 3.1 KiB |
|
@ -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
|
|
@ -1 +0,0 @@
|
||||||
python3 (>= 3.9), python3-pip, python3-pyside6.qtcore (>= 6), python3-pyside6.qtgui (>= 6), python3-pyside6.qtqml (>= 6), python3-pyside6.qtwidgets (>= 6), python3-pyside6.qtquick (>= 6), python3-pyside6.qtquickcontrols2 (>= 6), qml6-module-qt-labs-platform (>= 6), qml6-module-qtquick-dialogs (>= 6), texlive-latex-base, dvipng
|
|
|
@ -66,54 +66,50 @@
|
||||||
<categories>
|
<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>
|
||||||
|
|
||||||
|
|
|
@ -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/>
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
LPFv1{"xzoom":100,"yzoom":10,"xmin":0.2696454905834007,"ymax":33.115625,"xaxisstep":"4","yaxisstep":"π","xaxislabel":"","yaxislabel":"","logscalex":true,"linewidth":1,"showxgrad":true,"showygrad":true,"textsize":18,"history":[[["CreateNewObject",["A","Point",["A",true,"#941A97","name + value","1","0","above","●"]]],["EditedPosition",["A","Point","1","175.36","0","9.9"]],["CreateNewObject",["f","Function",["f",true,"#6E590E","name + value","x","ℝ⁺*","ℝ","application","above",1,true,true]]],["EditedProperty",["f","Function","expression","x","((x / 2) - 1)",true]],["CreateNewObject",["t","Text",["t",true,"#118455","null","1","0","center","New text",false]]],["EditedPosition",["t","Text","1","36.48","0","(-13.7)"]],["EditedProperty",["t","Text","text","New text","AEZA",false]],["CreateNewObject",["ω","Point",["ω",true,"#5A3A52","name","1","0","above","●"]]],["CreateNewObject",["G₀","Gain Bode",["G₀",true,"#5A3A52","name + value","ω","high","20","below",1,false]]],["EditedPosition",["ω","Point","1","17.76","0","(-8.9)"]],["EditedProperty",["G₀","Gain Bode","gain","20","10",true]],["EditedProperty",["G₀","Gain Bode","labelPosition","below","below-left",false]],["EditedProperty",["G₀","Gain Bode","pass","high","low",false]],["EditedProperty",["G₀","Gain Bode","labelX",1,62.61,false]],["CreateNewObject",["X","X Cursor",["X",true,"#5909A9","name + value","1",null,"left",true,3,"— — — — — — —","Next to target"]]],["EditedProperty",["X","X Cursor","x","1","5.04",true]],["CreateNewObject",["u","Sequence",["u",true,"#78929E","name + value",true,true,{"1":"n"},{"0":0},"above",1]]],["EditedProperty",["u","Sequence","defaultExpression",{"1":"n"},{"1":"n+1"},false]],["EditedProperty",["u","Sequence","defaultExpression",{"1":"n+1"},{"1":"n+1"},false]],["EditedProperty",["u","Sequence","baseValues",{"0":0},{"0":"-1"},false]],["EditedProperty",["u","Sequence","baseValues",{"0":"-1"},{"0":"-1"},false]],["CreateNewObject",["F_X","Repartition",["F_X",true,"#231931","name + value",{"0":"0"},"above",1]]],["EditedProperty",["F_X","Repartition","labelX",1,12.64,false]],["EditedProperty",["f","Function","labelPosition","above","right",false]],["EditedProperty",["f","Function","labelX",1,30,false]],["EditedProperty",["u","Sequence","labelX",1,3,false]],["EditedProperty",["F_X","Repartition","labelX",12.64,40,false]],["EditedProperty",["ω","Point","labelPosition","above","below",false]],["CreateNewObject",["ω₀","Point",["ω₀","#7C2981","name","name + value","1","0","above","●"]]],["CreateNewObject",["φ₀","Phase Bode",["φ₀",true,"#7C2981","name + value","ω₀","90","°","below",1]]],["EditedPosition",["ω₀","Point","1","3","0","(-8)"]],["EditedPosition",["ω₀","Point","3","2","(-8)","8"]],["EditedProperty",["ω₀","Point","labelPosition","above","above-right",false]],["EditedProperty",["u","Sequence","labelPosition","above","above-left",false]],["EditedProperty",["u","Sequence","labelX",3,20,false]],["EditedProperty",["G","Somme gains Bode","labelX",1,2,false]]],[]],"width":1000,"height":500,"objects":{"Point":[["A",true,"#941A97","name + value","175.36","9.9","above","●"],["ω",true,"#5A3A52","name","17.76","(-8.9)","below","●"],["ω₀",false,"name","name","2","8","above-right","●"]],"Function":[["f",true,"#6E590E","name + value","((x / 2) - 1)","ℝ⁺*","ℝ","application","right",30,true,true]],"Text":[["t",true,"#118455","null","36.48","(-13.7)","center","AEZA",false]],"Gain Bode":[["G₀",true,"#5A3A52","name + value","ω","low","10","below-left",62.61,false]],"Somme gains Bode":[["G",true,"#A83C72","name + value","above",2]],"X Cursor":[["X",true,"#5909A9","name + value","5.04",null,"left",true,null,"— — — — — — —","Next to target"]],"Sequence":[["u",true,"#78929E","name + value",true,true,{"1":"n+1"},{"0":"-1"},"above-left",20]],"Repartition":[["F_X",true,"#231931","name + value",{"0":"0"},"above",40]],"Phase Bode":[["φ₀",true,"#7C2981","name + value","ω₀","90","°","below",1]],"Somme phases Bode":[["φ",true,"#A08B14","name + value","above",1]]},"type":"logplotv1"}
|
|
17
ci/drone.yml
|
@ -12,29 +12,32 @@ steps:
|
||||||
- git submodule update --init --recursive
|
- 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 ]
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
LPFv1{"xzoom":100,"yzoom":100,"xmin":0.5,"ymax":2,"xaxisstep":"4","yaxisstep":"pi/4","xaxislabel":"ω (rad/s)","yaxislabel":"φ (rad)","logscalex":true,"linewidth":2,"showxgrad":true,"showygrad":true,"textsize":20,"history":[[["CreateNewObject",["f","Function",["f",true,"#989E2D","name + value","x","ℝ⁺*","ℝ","application","above",1,true,true]]],["EditedProperty",["f","Function","expression","x","(x ^ 20)",true]],["EditedProperty",["f","Function","expression","(x ^ 20)","(20 * (log10 x))",true]],["DeleteObject",["f","Function",["f",true,"#989E2D","name + value","(20 * (log10 x))","ℝ⁺*","ℝ","application","above",1,true,true]]],["CreateNewObject",["ω","Point",["ω",true,"#995178","name","1","0","bottom","●"]]],["CreateNewObject",["φ₀","Phase Bode",["φ₀",true,"#995178","name + value","ω","90","°","below",1]]],["EditedProperty",["φ₀","Phase Bode","phase","90","0",true]],["EditedProperty",["φ₀","Phase Bode","unit","°","rad",false]],["EditedProperty",["ω","Point","y","0","((-pi) / 2)",true]],["EditedVisibility",["ω","Point","visible"]],["EditedProperty",["φ₀","Phase Bode","labelX",1,10,false]],["CreateNewObject",["ω₀","Point",["ω₀",true,"#037753","name","1","0","bottom","●"]]],["CreateNewObject",["φ₁","Phase Bode",["φ₁",true,"#037753","name + value","ω₀","90","°","below",1]]],["EditedProperty",["ω₀","Point","x","1","10",true]],["EditedProperty",["φ₁","Phase Bode","unit","°","rad",false]],["EditedProperty",["φ₁","Phase Bode","phase","90","(pi / 2)",true]],["EditedProperty",["ω₀","Point","x","10","5",true]],["EditedProperty",["ω₀","Point","labelPosition","bottom","top-left",false]],["EditedProperty",["φ₁","Phase Bode","labelX",1,2,false]],["EditedProperty",["φ","Somme phases Bode","labelX",1,2,false]],["ColorChanged",["φ","Somme phases Bode","#665B74","#550000"]],["EditedProperty",["ω₀","Point","labelPosition","top-left","above-left",false]],["EditedProperty",["ω₀","Point","labelPosition","above-left","above-right",false]]],[]],"width":1000,"height":500,"objects":{"Function":[],"Point":[["ω",false,"#995178","name","1","((-pi) / 2)","below","●"],["ω₀",true,"#037753","name","5","0","above-right","●"]],"Phase Bode":[["φ₀",true,"#995178","name","ω","0","rad","below",10],["φ₁",true,"#037753","name","ω₀","(pi / 2)","rad","below",2]],"Somme phases Bode":[["φ",true,"#550000","name + value","above",2]]},"type":"logplotv1"}
|
|
1539
common/package-lock.json
generated
|
@ -2,7 +2,7 @@
|
||||||
"name": "logarithmplotter",
|
"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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -1,116 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We do not inherit the DOM's Event, because not only the DOM part is unnecessary,
|
|
||||||
* but also because it does not exist within Qt environments.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
export class BaseEvent {
|
|
||||||
___name = ""
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @property {string} name - Name of the event.
|
|
||||||
*/
|
|
||||||
constructor(name) {
|
|
||||||
this.___name = name
|
|
||||||
}
|
|
||||||
|
|
||||||
get name() {
|
|
||||||
return this.___name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base class for all classes which can emit events.
|
|
||||||
*/
|
|
||||||
export class BaseEventEmitter {
|
|
||||||
static emits = []
|
|
||||||
|
|
||||||
/** @type {Record<string, Set<function>>} */
|
|
||||||
#listeners = {}
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
for(const eventType of this.constructor.emits) {
|
|
||||||
this.#listeners[eventType] = new Set()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a listener to an event that can be emitted by this object.
|
|
||||||
*
|
|
||||||
* @param {string} eventType - Name of the event to listen to. Throws an error if this object does not emit this kind of event.
|
|
||||||
* @param {function(BaseEvent)} eventListener - The function to be called back when the event is emitted.
|
|
||||||
*/
|
|
||||||
on(eventType, eventListener) {
|
|
||||||
if(eventType.includes(" ")) // Listen to several different events with the same listener.
|
|
||||||
for(const type of eventType.split(" "))
|
|
||||||
this.on(type, eventListener)
|
|
||||||
else {
|
|
||||||
if(!this.constructor.emits.includes(eventType)) {
|
|
||||||
const className = this.constructor.name
|
|
||||||
const eventTypes = this.constructor.emits.join(", ")
|
|
||||||
throw new Error(`Cannot listen to unknown event ${eventType} in class ${className}. ${className} only emits: ${eventTypes}`)
|
|
||||||
}
|
|
||||||
if(!this.#listeners[eventType].has(eventListener))
|
|
||||||
this.#listeners[eventType].add(eventListener)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a listener from an event that can be emitted by this object.
|
|
||||||
*
|
|
||||||
* @param {string} eventType - Name of the event that was listened to. Throws an error if this object does not emit this kind of event.
|
|
||||||
* @param {function(BaseEvent)} eventListener - The function previously registered as a listener.
|
|
||||||
* @returns {boolean} True if the listener was removed, false if it was not found.
|
|
||||||
*/
|
|
||||||
off(eventType, eventListener) {
|
|
||||||
if(eventType.includes(" ")) { // Unlisten to several different events with the same listener.
|
|
||||||
let found = false
|
|
||||||
for(const type of eventType.split(" "))
|
|
||||||
found ||= this.off(type, eventListener)
|
|
||||||
return found
|
|
||||||
} else {
|
|
||||||
if(!this.constructor.emits.includes(eventType)) {
|
|
||||||
const className = this.constructor.name
|
|
||||||
const eventTypes = this.constructor.emits.join(", ")
|
|
||||||
throw new Error(`Cannot listen to unknown event ${eventType} in class ${className}. ${className} only emits: ${eventTypes}`)
|
|
||||||
}
|
|
||||||
return this.#listeners[eventType].delete(eventListener)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emits an event to all of its listeners.
|
|
||||||
*
|
|
||||||
* @param {BaseEvent} e
|
|
||||||
*/
|
|
||||||
emit(e) {
|
|
||||||
if(!(e instanceof BaseEvent))
|
|
||||||
throw new Error("Cannot emit non event object.")
|
|
||||||
if(!this.constructor.emits.includes(e.name)) {
|
|
||||||
const className = this.constructor.name
|
|
||||||
const eventTypes = this.constructor.emits.join(", ")
|
|
||||||
throw new Error(`Cannot emit event '${e.name}' from class ${className}. ${className} can only emit: ${eventTypes}`)
|
|
||||||
}
|
|
||||||
for(const listener of this.#listeners[e.name])
|
|
||||||
listener(e)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -95,15 +95,11 @@ export class Action {
|
||||||
if(!Latex.enabled)
|
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"/>`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 || {}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) === "=") {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/*!
|
/**
|
||||||
* 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-2024 Ad5001
|
* Copyright (C) 2021-2024 Ad5001
|
||||||
*
|
*
|
||||||
|
@ -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: [
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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,18 +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!")
|
||||||
this.#redrawCount = (this.#redrawCount + 1) % 10000
|
this._ctx = this._canvas.getContext("2d")
|
||||||
this.#ctx = this.#canvas.getContext("2d")
|
|
||||||
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)
|
||||||
|
@ -231,12 +227,12 @@ class CanvasAPI extends Module {
|
||||||
// Drawing throws an error. Generally, it's due to a new modification (or the opening of a file)
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -244,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: {
|
||||||
|
@ -268,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -279,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++) {
|
||||||
|
@ -303,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)
|
||||||
|
@ -324,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)))
|
||||||
}
|
}
|
||||||
|
@ -354,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"
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -398,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))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -413,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -428,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 }
|
||||||
}
|
}
|
||||||
|
@ -498,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()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -513,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([])
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -525,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)
|
||||||
|
@ -555,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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -572,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()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -584,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()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -597,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/**
|
/*!
|
||||||
* 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>
|
||||||
|
@ -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
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -1,186 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Module } from "./common.mjs"
|
|
||||||
import { BaseEvent } from "../events.mjs"
|
|
||||||
import { HelperInterface } from "./interface.mjs"
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base event for when a setting was changed.
|
|
||||||
*/
|
|
||||||
class ChangedEvent extends BaseEvent {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} property - Name of the property that was chagned
|
|
||||||
* @param {string|number|boolean} oldValue - Old value of the property
|
|
||||||
* @param {string|number|boolean} newValue - Current (new) value of the property
|
|
||||||
* @param {boolean} byUser - True if the user is at the source of the change in the setting.
|
|
||||||
*/
|
|
||||||
constructor(property, oldValue, newValue, byUser) {
|
|
||||||
super("changed")
|
|
||||||
|
|
||||||
this.property = property
|
|
||||||
this.oldValue = oldValue
|
|
||||||
this.newValue = newValue
|
|
||||||
this.byUser = byUser
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module for graph settings.
|
|
||||||
*/
|
|
||||||
class SettingsAPI extends Module {
|
|
||||||
static emits = ["changed"]
|
|
||||||
|
|
||||||
#nonConfigurable = ["saveFilename"]
|
|
||||||
|
|
||||||
/** @type {Map<string, string|number|boolean>} */
|
|
||||||
#properties = new Map([
|
|
||||||
["saveFilename", ""],
|
|
||||||
["xzoom", 100],
|
|
||||||
["yzoom", 10],
|
|
||||||
["xmin", .5],
|
|
||||||
["ymax", 25],
|
|
||||||
["xaxisstep", "4"],
|
|
||||||
["yaxisstep", "4"],
|
|
||||||
["xlabel", ""],
|
|
||||||
["ylabel", ""],
|
|
||||||
["linewidth", 1],
|
|
||||||
["textsize", 18],
|
|
||||||
["logscalex", true],
|
|
||||||
["showxgrad", true],
|
|
||||||
["showygrad", true]
|
|
||||||
])
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super("Settings", {
|
|
||||||
helper: HelperInterface
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {HelperInterface} helper
|
|
||||||
*/
|
|
||||||
initialize({ helper }) {
|
|
||||||
super.initialize({ helper })
|
|
||||||
// Initialize default values.
|
|
||||||
for(const key of this.#properties.keys())
|
|
||||||
if(!this.#nonConfigurable.includes(key))
|
|
||||||
this.set(key, helper.getSetting("default_graph."+key), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a setting to a given value
|
|
||||||
*
|
|
||||||
* @param {string} property
|
|
||||||
* @param {string|number|boolean} value
|
|
||||||
* @param {boolean} byUser - Set to true if the user is at the origin of this change.
|
|
||||||
*/
|
|
||||||
set(property, value, byUser) {
|
|
||||||
if(!this.#properties.has(property))
|
|
||||||
throw new Error(`Property ${property} is not a setting.`)
|
|
||||||
const oldValue = this.#properties.get(property)
|
|
||||||
const propType = typeof oldValue
|
|
||||||
if(byUser)
|
|
||||||
console.debug("Setting", property, "from", oldValue, "to", value, `(${typeof value}, ${byUser})`)
|
|
||||||
if(propType !== typeof value)
|
|
||||||
throw new Error(`Value of ${property} must be a ${propType} (${typeof value} provided).`)
|
|
||||||
this.#properties.set(property, value)
|
|
||||||
const evt = new ChangedEvent(property, oldValue, value, byUser === true)
|
|
||||||
this.emit(evt)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Name of the currently opened file.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
get saveFilename() { return this.#properties.get("saveFilename") }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zoom on the x axis of the diagram.
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
get xzoom() { return this.#properties.get("xzoom") }
|
|
||||||
/**
|
|
||||||
* Zoom on the y axis of the diagram.
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
get yzoom() { return this.#properties.get("yzoom") }
|
|
||||||
/**
|
|
||||||
* Minimum x of the diagram.
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
get xmin() { return this.#properties.get("xmin") }
|
|
||||||
/**
|
|
||||||
* Maximum y of the diagram.
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
get ymax() { return this.#properties.get("ymax") }
|
|
||||||
/**
|
|
||||||
* Step of the x axis graduation (expression).
|
|
||||||
* @note Only available in non-logarithmic mode.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
get xaxisstep() { return this.#properties.get("xaxisstep") }
|
|
||||||
/**
|
|
||||||
* Step of the y axis graduation (expression).
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
get yaxisstep() { return this.#properties.get("yaxisstep") }
|
|
||||||
/**
|
|
||||||
* Label used on the x axis.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
get xlabel() { return this.#properties.get("xlabel") }
|
|
||||||
/**
|
|
||||||
* Label used on the y axis.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
get ylabel() { return this.#properties.get("ylabel") }
|
|
||||||
/**
|
|
||||||
* Width of lines that will be drawn into the canvas.
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
get linewidth() { return this.#properties.get("linewidth") }
|
|
||||||
/**
|
|
||||||
* Font size of the text that will be drawn into the canvas.
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
get textsize() { return this.#properties.get("textsize") }
|
|
||||||
/**
|
|
||||||
* true if the canvas should be in logarithmic mode, false otherwise.
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
get logscalex() { return this.#properties.get("logscalex") }
|
|
||||||
/**
|
|
||||||
* true if the x graduation should be shown, false otherwise.
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
get showxgrad() { return this.#properties.get("showxgrad") }
|
|
||||||
/**
|
|
||||||
* true if the y graduation should be shown, false otherwise.
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
get showygrad() { return this.#properties.get("showygrad") }
|
|
||||||
}
|
|
||||||
|
|
||||||
Modules.Settings = Modules.Settings || new SettingsAPI()
|
|
||||||
export default Modules.Settings
|
|
||||||
|
|
|
@ -63,7 +63,7 @@ export default class BodePhase extends ExecutableObject {
|
||||||
// Create new point
|
// 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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* 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 { textsub } from "../utils/index.mjs"
|
import { textsub } from "../utils.mjs"
|
||||||
import Objects from "../module/objects.mjs"
|
import Objects from "../module/objects.mjs"
|
||||||
import { ExecutableObject } from "./common.mjs"
|
import { ExecutableObject } from "./common.mjs"
|
||||||
import { parseDomain, Expression, SpecialDomain } from "../math/index.mjs"
|
import { parseDomain, Expression, SpecialDomain } from "../math/index.mjs"
|
||||||
|
@ -122,9 +122,7 @@ export default class Function extends ExecutableObject {
|
||||||
*/
|
*/
|
||||||
static drawFunction(canvas, expr, definitionDomain, destinationDomain, drawPoints = true, drawDash = true) {
|
static drawFunction(canvas, expr, definitionDomain, destinationDomain, drawPoints = true, drawDash = true) {
|
||||||
let pxprecision = 10
|
let pxprecision = 10
|
||||||
const startDrawFrom = canvas.x2px(1)%pxprecision-pxprecision
|
let previousX = canvas.px2x(0)
|
||||||
let previousX = canvas.px2x(startDrawFrom)
|
|
||||||
// console.log("Starting draw from", previousX, startDrawFrom, canvas.x2px(1))
|
|
||||||
let previousY = null
|
let previousY = null
|
||||||
if(definitionDomain instanceof SpecialDomain && definitionDomain.moveSupported) {
|
if(definitionDomain instanceof SpecialDomain && definitionDomain.moveSupported) {
|
||||||
// Point based functions.
|
// Point based functions.
|
||||||
|
@ -162,7 +160,7 @@ export default class Function extends ExecutableObject {
|
||||||
// Calculate the previousY at the start of the canvas
|
// Calculate the previousY at the start of the canvas
|
||||||
if(definitionDomain.includes(previousX))
|
if(definitionDomain.includes(previousX))
|
||||||
previousY = expr.execute(previousX)
|
previousY = expr.execute(previousX)
|
||||||
for(let px = pxprecision; px-pxprecision < canvas.width; px += pxprecision) {
|
for(let px = pxprecision; px < canvas.width; px += pxprecision) {
|
||||||
let currentX = canvas.px2x(px)
|
let currentX = canvas.px2x(px)
|
||||||
if(!definitionDomain.includes(previousX) && definitionDomain.includes(currentX)) {
|
if(!definitionDomain.includes(previousX) && definitionDomain.includes(currentX)) {
|
||||||
// Should draw up to currentX, but NOT at previousX.
|
// Should draw up to currentX, but NOT at previousX.
|
||||||
|
@ -171,7 +169,7 @@ export default class Function extends ExecutableObject {
|
||||||
do {
|
do {
|
||||||
tmpPx++
|
tmpPx++
|
||||||
previousX = canvas.px2x(tmpPx)
|
previousX = canvas.px2x(tmpPx)
|
||||||
} while(!definitionDomain.includes(previousX) && currentX > previousX)
|
} while(!definitionDomain.includes(previousX))
|
||||||
// Recaclulate previousY
|
// Recaclulate previousY
|
||||||
previousY = expr.execute(previousX)
|
previousY = expr.execute(previousX)
|
||||||
} else if(!definitionDomain.includes(currentX)) {
|
} else if(!definitionDomain.includes(currentX)) {
|
||||||
|
@ -181,7 +179,7 @@ export default class Function extends ExecutableObject {
|
||||||
do {
|
do {
|
||||||
tmpPx--
|
tmpPx--
|
||||||
currentX = canvas.px2x(tmpPx)
|
currentX = canvas.px2x(tmpPx)
|
||||||
} while(!definitionDomain.includes(currentX) && currentX > previousX)
|
} while(!definitionDomain.includes(currentX) && currentX !== previousX)
|
||||||
}
|
}
|
||||||
// This max variation is needed for functions with asymptotical vertical lines (e.g. 1/x, tan x...)
|
// This max variation is needed for functions with asymptotical vertical lines (e.g. 1/x, tan x...)
|
||||||
let maxvariation = (canvas.px2y(0) - canvas.px2y(canvas.height))
|
let maxvariation = (canvas.px2y(0) - canvas.px2y(canvas.height))
|
||||||
|
|
|
@ -53,11 +53,11 @@ export class BoolSetting extends Setting {
|
||||||
}
|
}
|
||||||
|
|
||||||
value() {
|
value() {
|
||||||
return Helper.getSetting(this.nameInConfig)
|
return Helper.getSettingBool(this.nameInConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
set(value) {
|
set(value) {
|
||||||
Helper.setSetting(this.nameInConfig, value === true)
|
Helper.setSettingBool(this.nameInConfig, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,11 +69,11 @@ export class NumberSetting extends Setting {
|
||||||
}
|
}
|
||||||
|
|
||||||
value() {
|
value() {
|
||||||
return Helper.getSetting(this.nameInConfig)
|
return Helper.getSettingInt(this.nameInConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
set(value) {
|
set(value) {
|
||||||
Helper.setSetting(this.nameInConfig, +value)
|
Helper.setSettingInt(this.nameInConfig, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,11 +84,11 @@ export class EnumIntSetting extends Setting {
|
||||||
}
|
}
|
||||||
|
|
||||||
value() {
|
value() {
|
||||||
return Helper.getSetting(this.nameInConfig)
|
return Helper.getSettingInt(this.nameInConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
set(value) {
|
set(value) {
|
||||||
Helper.setSetting(this.nameInConfig, +value)
|
Helper.setSettingInt(this.nameInConfig, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,6 +131,6 @@ export class StringSetting extends Setting {
|
||||||
}
|
}
|
||||||
|
|
||||||
set(value) {
|
set(value) {
|
||||||
Helper.setSetting(this.nameInConfig, ""+value)
|
Helper.setSetting(this.nameInConfig, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -28,7 +28,7 @@ const XZOOM = new NumberSetting(
|
||||||
|
|
||||||
const YZOOM = new NumberSetting(
|
const YZOOM = new NumberSetting(
|
||||||
qsTranslate("Settings", "Y Zoom"),
|
qsTranslate("Settings", "Y Zoom"),
|
||||||
"default_graph.yzoom",
|
"default_graph.xzoom",
|
||||||
"yzoom",
|
"yzoom",
|
||||||
0.1
|
0.1
|
||||||
)
|
)
|
||||||
|
@ -37,7 +37,7 @@ const XMIN = new NumberSetting(
|
||||||
qsTranslate("Settings", "Min X"),
|
qsTranslate("Settings", "Min X"),
|
||||||
"default_graph.xmin",
|
"default_graph.xmin",
|
||||||
"xmin",
|
"xmin",
|
||||||
() => Helper.getSetting("default_graph.logscalex") ? 1e-100 : -Infinity
|
() => Helper.getSettingBool("default_graph.logscalex") ? 1e-100 : -Infinity
|
||||||
)
|
)
|
||||||
|
|
||||||
const YMAX = new NumberSetting(
|
const YMAX = new NumberSetting(
|
||||||
|
|
|
@ -46,15 +46,8 @@ class EnableLatex extends BoolSetting {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ENABLE_LATEX_ASYNC = new BoolSetting(
|
|
||||||
qsTranslate("general", "Enable threaded LaTeX renderer (experimental)"),
|
|
||||||
"enable_latex_threaded",
|
|
||||||
"new"
|
|
||||||
)
|
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
CHECK_FOR_UPDATES,
|
CHECK_FOR_UPDATES,
|
||||||
RESET_REDO_STACK,
|
RESET_REDO_STACK,
|
||||||
new EnableLatex(),
|
new EnableLatex()
|
||||||
ENABLE_LATEX_ASYNC
|
|
||||||
]
|
]
|
||||||
|
|
414
common/src/utils.mjs
Normal file
|
@ -0,0 +1,414 @@
|
||||||
|
/**
|
||||||
|
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||||
|
* Copyright (C) 2021-2024 Ad5001
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Add string methods
|
||||||
|
/**
|
||||||
|
* Replaces latin characters with their uppercase versions.
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
String.prototype.toLatinUppercase = String.prototype.toLatinUppercase || function() {
|
||||||
|
return this.replace(/[a-z]/g, function(match) {
|
||||||
|
return match.toUpperCase()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the 'enclosers' of a string (e.g. quotes, parentheses, brackets...)
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
String.prototype.removeEnclosure = function() {
|
||||||
|
return this.substring(1, this.length - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const powerpos = {
|
||||||
|
"-": "⁻",
|
||||||
|
"+": "⁺",
|
||||||
|
"=": "⁼",
|
||||||
|
" ": " ",
|
||||||
|
"(": "⁽",
|
||||||
|
")": "⁾",
|
||||||
|
"0": "⁰",
|
||||||
|
"1": "¹",
|
||||||
|
"2": "²",
|
||||||
|
"3": "³",
|
||||||
|
"4": "⁴",
|
||||||
|
"5": "⁵",
|
||||||
|
"6": "⁶",
|
||||||
|
"7": "⁷",
|
||||||
|
"8": "⁸",
|
||||||
|
"9": "⁹",
|
||||||
|
"a": "ᵃ",
|
||||||
|
"b": "ᵇ",
|
||||||
|
"c": "ᶜ",
|
||||||
|
"d": "ᵈ",
|
||||||
|
"e": "ᵉ",
|
||||||
|
"f": "ᶠ",
|
||||||
|
"g": "ᵍ",
|
||||||
|
"h": "ʰ",
|
||||||
|
"i": "ⁱ",
|
||||||
|
"j": "ʲ",
|
||||||
|
"k": "ᵏ",
|
||||||
|
"l": "ˡ",
|
||||||
|
"m": "ᵐ",
|
||||||
|
"n": "ⁿ",
|
||||||
|
"o": "ᵒ",
|
||||||
|
"p": "ᵖ",
|
||||||
|
"r": "ʳ",
|
||||||
|
"s": "ˢ",
|
||||||
|
"t": "ᵗ",
|
||||||
|
"u": "ᵘ",
|
||||||
|
"v": "ᵛ",
|
||||||
|
"w": "ʷ",
|
||||||
|
"x": "ˣ",
|
||||||
|
"y": "ʸ",
|
||||||
|
"z": "ᶻ"
|
||||||
|
}
|
||||||
|
|
||||||
|
const exponents = [
|
||||||
|
"⁰","¹","²","³","⁴","⁵","⁶","⁷","⁸","⁹"
|
||||||
|
]
|
||||||
|
|
||||||
|
const exponentReg = new RegExp('(['+exponents.join('')+']+)', 'g')
|
||||||
|
|
||||||
|
const indicepos = {
|
||||||
|
"-": "₋",
|
||||||
|
"+": "₊",
|
||||||
|
"=": "₌",
|
||||||
|
"(": "₍",
|
||||||
|
")": "₎",
|
||||||
|
" ": " ",
|
||||||
|
"0": "₀",
|
||||||
|
"1": "₁",
|
||||||
|
"2": "₂",
|
||||||
|
"3": "₃",
|
||||||
|
"4": "₄",
|
||||||
|
"5": "₅",
|
||||||
|
"6": "₆",
|
||||||
|
"7": "₇",
|
||||||
|
"8": "₈",
|
||||||
|
"9": "₉",
|
||||||
|
"a": "ₐ",
|
||||||
|
"e": "ₑ",
|
||||||
|
"h": "ₕ",
|
||||||
|
"i": "ᵢ",
|
||||||
|
"j": "ⱼ",
|
||||||
|
"k": "ₖ",
|
||||||
|
"l": "ₗ",
|
||||||
|
"m": "ₘ",
|
||||||
|
"n": "ₙ",
|
||||||
|
"o": "ₒ",
|
||||||
|
"p": "ₚ",
|
||||||
|
"r": "ᵣ",
|
||||||
|
"s": "ₛ",
|
||||||
|
"t": "ₜ",
|
||||||
|
"u": "ᵤ",
|
||||||
|
"v": "ᵥ",
|
||||||
|
"x": "ₓ",
|
||||||
|
}
|
||||||
|
// Put a text in sup position
|
||||||
|
export function textsup(text) {
|
||||||
|
let ret = ""
|
||||||
|
text = text.toString()
|
||||||
|
for (let i = 0; i < text.length; i++) {
|
||||||
|
if(Object.keys(powerpos).indexOf(text[i]) >= 0) {
|
||||||
|
ret += powerpos[text[i]]
|
||||||
|
} else {
|
||||||
|
ret += text[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put a text in sub position
|
||||||
|
export function textsub(text) {
|
||||||
|
let ret = ""
|
||||||
|
text = text.toString()
|
||||||
|
for (let i = 0; i < text.length; i++) {
|
||||||
|
if(Object.keys(indicepos).indexOf(text[i]) >= 0) {
|
||||||
|
ret += indicepos[text[i]]
|
||||||
|
} else {
|
||||||
|
ret += text[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simplifies (mathematically) a mathematical expression.
|
||||||
|
* @param {string} str - Expression to parse
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function simplifyExpression(str) {
|
||||||
|
let replacements = [
|
||||||
|
// Operations not done by parser.
|
||||||
|
// [// Decomposition way 2
|
||||||
|
// /(^|[+-] |\()([-.\d\w]+) ([*/]) \((([-.\d\w] [*/] )?[-\d\w.]+) ([+\-]) (([-.\d\w] [*/] )?[\d\w.+]+)\)($| [+-]|\))/g,
|
||||||
|
// "$1$2 $3 $4 $6 $2 $3 $7$9"
|
||||||
|
// ],
|
||||||
|
// [ // Decomposition way 2
|
||||||
|
// /(^|[+-] |\()\((([-.\d\w] [*/] )?[-\d\w.]+) ([+\-]) (([-.\d\w] [*/] )?[\d\w.+]+)\) ([*/]) ([-.\d\w]+)($| [+-]|\))/g,
|
||||||
|
// "$1$2 $7 $8 $4 $5 $7 $8$9"
|
||||||
|
// ],
|
||||||
|
[ // Factorisation of π elements.
|
||||||
|
/(([-\d\w.]+ [*/] )*)(pi|π)(( [/*] [-\d\w.]+)*) ([+-]) (([-\d\w.]+ [*/] )*)(pi|π)(( [/*] [-\d\w.]+)*)?/g,
|
||||||
|
function(match, m1, n1, pi1, m2, ope2, n2, opeM, m3, n3, pi2, m4, ope4, n4) {
|
||||||
|
// g1, g2, g3 , g4, g5, g6, g7, g8, g9, g10, g11,g12 , g13
|
||||||
|
// We don't care about mx & pix, ope2 & ope4 are either / or * for n2 & n4.
|
||||||
|
// n1 & n3 are multiplied, opeM is the main operation (- or +).
|
||||||
|
// Putting all n in form of number
|
||||||
|
//n2 = n2 == undefined ? 1 : parseFloat(n)
|
||||||
|
n1 = m1 === undefined ? 1 : eval(m1 + '1')
|
||||||
|
n2 = m2 === undefined ? 1 : eval('1' + m2)
|
||||||
|
n3 = m3 === undefined ? 1 : eval(m3 + '1')
|
||||||
|
n4 = m4 === undefined ? 1 : eval('1' + m4)
|
||||||
|
//let [n1, n2, n3, n4] = [n1, n2, n3, n4].map(n => n == undefined ? 1 : parseFloat(n))
|
||||||
|
// Falling back to * in case it does not exist (the corresponding n would be 1)
|
||||||
|
[ope2, ope4] = [ope2, ope4].map(ope => ope === '/' ? '/' : '*')
|
||||||
|
let coeff1 = n1*n2
|
||||||
|
let coeff2 = n3*n4
|
||||||
|
let coefficient = coeff1+coeff2-(opeM === '-' ? 2*coeff2 : 0)
|
||||||
|
|
||||||
|
return `${coefficient} * π`
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[ // Removing parenthesis when content is only added from both sides.
|
||||||
|
/(^|[+-] |\()\(([^)(]+)\)($| [+-]|\))/g,
|
||||||
|
function(match, b4, middle, after) {return `${b4}${middle}${after}`}
|
||||||
|
],
|
||||||
|
[ // Removing parenthesis when content is only multiplied.
|
||||||
|
/(^|[*\/] |\()\(([^)(+-]+)\)($| [*\/+-]|\))/g,
|
||||||
|
function(match, b4, middle, after) {return `${b4}${middle}${after}`}
|
||||||
|
],
|
||||||
|
[ // Removing parenthesis when content is only multiplied.
|
||||||
|
/(^|[*\/+-] |\()\(([^)(+-]+)\)($| [*\/]|\))/g,
|
||||||
|
function(match, b4, middle, after) {return `${b4}${middle}${after}`}
|
||||||
|
],
|
||||||
|
[// Simplification additions/subtractions.
|
||||||
|
/(^|[^*\/] |\()([-.\d]+) [+-] (\([^)(]+\)|[^)(]+) [+-] ([-.\d]+)($| [^*\/]|\))/g,
|
||||||
|
function(match, b4, n1, op1, middle, op2, n2, after) {
|
||||||
|
let total
|
||||||
|
if(op2 === '+') {
|
||||||
|
total = parseFloat(n1) + parseFloat(n2)
|
||||||
|
} else {
|
||||||
|
total = parseFloat(n1) - parseFloat(n2)
|
||||||
|
}
|
||||||
|
return `${b4}${total} ${op1} ${middle}${after}`
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[// Simplification multiplications/divisions.
|
||||||
|
/([-.\d]+) [*\/] (\([^)(]+\)|[^)(+-]+) [*\/] ([-.\d]+)/g,
|
||||||
|
function(match, n1, op1, middle, op2, n2) {
|
||||||
|
if(parseInt(n1) === n1 && parseInt(n2) === n2 && op2 === '/' &&
|
||||||
|
(parseInt(n1) / parseInt(n2)) % 1 !== 0) {
|
||||||
|
// Non int result for int division.
|
||||||
|
return `(${n1} / ${n2}) ${op1} ${middle}`
|
||||||
|
} else {
|
||||||
|
if(op2 === '*') {
|
||||||
|
return `${parseFloat(n1) * parseFloat(n2)} ${op1} ${middle}`
|
||||||
|
} else {
|
||||||
|
return `${parseFloat(n1) / parseFloat(n2)} ${op1} ${middle}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[// Starting & ending parenthesis if not needed.
|
||||||
|
/^\s*\((.*)\)\s*$/g,
|
||||||
|
function(match, middle) {
|
||||||
|
let str = middle
|
||||||
|
// Replace all groups
|
||||||
|
while(/\([^)(]+\)/g.test(str))
|
||||||
|
str = str.replace(/\([^)(]+\)/g, '')
|
||||||
|
// There shouldn't be any more parenthesis
|
||||||
|
// If there is, that means the 2 parenthesis are needed.
|
||||||
|
if(!str.includes(')') && !str.includes('(')) {
|
||||||
|
return middle
|
||||||
|
} else {
|
||||||
|
return `(${middle})`
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
],
|
||||||
|
// Simple simplifications
|
||||||
|
// [/(\s|^|\()0(\.0+)? \* (\([^)(]+\))/g, '$10'],
|
||||||
|
// [/(\s|^|\()0(\.0+)? \* ([^)(+-]+)/g, '$10'],
|
||||||
|
// [/(\([^)(]\)) \* 0(\.0+)?(\s|$|\))/g, '0$3'],
|
||||||
|
// [/([^)(+-]) \* 0(\.0+)?(\s|$|\))/g, '0$3'],
|
||||||
|
// [/(\s|^|\()1(\.0+)? [\*\/] /g, '$1'],
|
||||||
|
// [/(\s|^|\()0(\.0+)? (\+|\-) /g, '$1'],
|
||||||
|
// [/ [\*\/] 1(\.0+)?(\s|$|\))/g, '$3'],
|
||||||
|
// [/ (\+|\-) 0(\.0+)?(\s|$|\))/g, '$3'],
|
||||||
|
// [/(^| |\() /g, '$1'],
|
||||||
|
// [/ ($|\))/g, '$1'],
|
||||||
|
]
|
||||||
|
|
||||||
|
// Replacements
|
||||||
|
let found
|
||||||
|
do {
|
||||||
|
found = false
|
||||||
|
for(let replacement of replacements)
|
||||||
|
while(replacement[0].test(str)) {
|
||||||
|
found = true
|
||||||
|
str = str.replace(replacement[0], replacement[1])
|
||||||
|
}
|
||||||
|
} while(found)
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms a mathematical expression to make it readable by humans.
|
||||||
|
* NOTE: Will break parsing of expression.
|
||||||
|
* @param {string} str - Expression to parse.
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function makeExpressionReadable(str) {
|
||||||
|
let replacements = [
|
||||||
|
// letiables
|
||||||
|
[/pi/g, 'π'],
|
||||||
|
[/Infinity/g, '∞'],
|
||||||
|
[/inf/g, '∞'],
|
||||||
|
// Other
|
||||||
|
[/ \* /g, '×'],
|
||||||
|
[/ \^ /g, '^'],
|
||||||
|
[/\^\(([\d\w+-]+)\)/g, function(match, p1) { return textsup(p1) }],
|
||||||
|
[/\^([\d\w+-]+)/g, function(match, p1) { return textsup(p1) }],
|
||||||
|
[/_\(([\d\w+-]+)\)/g, function(match, p1) { return textsub(p1) }],
|
||||||
|
[/_([\d\w+-]+)/g, function(match, p1) { return textsub(p1) }],
|
||||||
|
[/\[([^\[\]]+)\]/g, function(match, p1) { return textsub(p1) }],
|
||||||
|
[/(\d|\))×/g, '$1'],
|
||||||
|
[/integral\((.+),\s?(.+),\s?["'](.+)["'],\s?["'](.+)["']\)/g, function(match, a, b, p1, body, p2, p3, by, p4) {
|
||||||
|
if(a.length < b.length) {
|
||||||
|
return `∫${textsub(a)}${textsup(b)} ${body} d${by}`
|
||||||
|
} else {
|
||||||
|
return `∫${textsup(b)}${textsub(a)} ${body} d${by}`
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
[/derivative\(?["'](.+)["'], ?["'](.+)["'], ?(.+)\)?/g, function(match, p1, body, p2, p3, by, p4, x) {
|
||||||
|
return `d(${body.replace(new RegExp(by, 'g'), 'x')})/dx`
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
|
||||||
|
// str = simplifyExpression(str)
|
||||||
|
// Replacements
|
||||||
|
for(let replacement of replacements)
|
||||||
|
while(replacement[0].test(str))
|
||||||
|
str = str.replace(replacement[0], replacement[1])
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a variable name to make it readable by humans.
|
||||||
|
*
|
||||||
|
* @param {string} str - Variable name to parse
|
||||||
|
* @param {boolean} removeUnallowed - Remove domain symbols disallowed in name.
|
||||||
|
* @returns {string} - The parsed name
|
||||||
|
*/
|
||||||
|
export function parseName(str, removeUnallowed = true) {
|
||||||
|
let replacements = [
|
||||||
|
// Greek letters
|
||||||
|
[/([^a-z]|^)al(pha)?([^a-z]|$)/g, '$1α$3'],
|
||||||
|
[/([^a-z]|^)be(ta)?([^a-z]|$)/g, '$1β$3'],
|
||||||
|
[/([^a-z]|^)ga(mma)?([^a-z]|$)/g, '$1γ$3'],
|
||||||
|
[/([^a-z]|^)de(lta)?([^a-z]|$)/g, '$1δ$3'],
|
||||||
|
[/([^a-z]|^)ep(silon)?([^a-z]|$)/g, '$1ε$3'],
|
||||||
|
[/([^a-z]|^)ze(ta)?([^a-z]|$)/g, '$1ζ$3'],
|
||||||
|
[/([^a-z]|^)et(a)?([^a-z]|$)/g, '$1η$3'],
|
||||||
|
[/([^a-z]|^)th(eta)?([^a-z]|$)/g, '$1θ$3'],
|
||||||
|
[/([^a-z]|^)io(ta)?([^a-z]|$)/g, '$1ι$3'],
|
||||||
|
[/([^a-z]|^)ka(ppa)([^a-z]|$)?/g, '$1κ$3'],
|
||||||
|
[/([^a-z]|^)la(mbda)?([^a-z]|$)/g, '$1λ$3'],
|
||||||
|
[/([^a-z]|^)mu([^a-z]|$)/g, '$1μ$2'],
|
||||||
|
[/([^a-z]|^)nu([^a-z]|$)/g, '$1ν$2'],
|
||||||
|
[/([^a-z]|^)xi([^a-z]|$)/g, '$1ξ$2'],
|
||||||
|
[/([^a-z]|^)rh(o)?([^a-z]|$)/g, '$1ρ$3'],
|
||||||
|
[/([^a-z]|^)si(gma)?([^a-z]|$)/g, '$1σ$3'],
|
||||||
|
[/([^a-z]|^)ta(u)?([^a-z]|$)/g, '$1τ$3'],
|
||||||
|
[/([^a-z]|^)up(silon)?([^a-z]|$)/g, '$1υ$3'],
|
||||||
|
[/([^a-z]|^)ph(i)?([^a-z]|$)/g, '$1φ$3'],
|
||||||
|
[/([^a-z]|^)ch(i)?([^a-z]|$)/g, '$1χ$3'],
|
||||||
|
[/([^a-z]|^)ps(i)?([^a-z]|$)/g, '$1ψ$3'],
|
||||||
|
[/([^a-z]|^)om(ega)?([^a-z]|$)/g, '$1ω$3'],
|
||||||
|
// Capital greek letters
|
||||||
|
[/([^a-z]|^)gga(mma)?([^a-z]|$)/g, '$1Γ$3'],
|
||||||
|
[/([^a-z]|^)gde(lta)?([^a-z]|$)/g, '$1Δ$3'],
|
||||||
|
[/([^a-z]|^)gth(eta)?([^a-z]|$)/g, '$1Θ$3'],
|
||||||
|
[/([^a-z]|^)gla(mbda)?([^a-z]|$)/g, '$1Λ$3'],
|
||||||
|
[/([^a-z]|^)gxi([^a-z]|$)/g, '$1Ξ$2'],
|
||||||
|
[/([^a-z]|^)gpi([^a-z]|$)/g, '$1Π$2'],
|
||||||
|
[/([^a-z]|^)gsi(gma)([^a-z]|$)?/g, '$1Σ$3'],
|
||||||
|
[/([^a-z]|^)gph(i)?([^a-z]|$)/g, '$1Φ$3'],
|
||||||
|
[/([^a-z]|^)gps(i)?([^a-z]|$)/g, '$1Ψ$3'],
|
||||||
|
[/([^a-z]|^)gom(ega)?([^a-z]|$)/g, '$1Ω$3'],
|
||||||
|
// Underscores
|
||||||
|
// [/_\(([^_]+)\)/g, function(match, p1) { return textsub(p1) }],
|
||||||
|
// [/_([^" ]+)/g, function(match, p1) { return textsub(p1) }],
|
||||||
|
// Array elements
|
||||||
|
[/\[([^\]\[]+)\]/g, function(match, p1) { return textsub(p1) }],
|
||||||
|
// Removing
|
||||||
|
[/[xπℝℕ\\∪∩\]\[ ()^/÷*×+=\d-]/g , ''],
|
||||||
|
]
|
||||||
|
if(!removeUnallowed) replacements.pop()
|
||||||
|
// Replacements
|
||||||
|
for(let replacement of replacements)
|
||||||
|
str = str.replace(replacement[0], replacement[1])
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms camel case strings to a space separated one.
|
||||||
|
*
|
||||||
|
* @param {string} label - Camel case to parse
|
||||||
|
* @returns {string} Parsed label.
|
||||||
|
*/
|
||||||
|
export function camelCase2readable(label) {
|
||||||
|
let parsed = parseName(label, false)
|
||||||
|
return parsed.charAt(0).toLatinUppercase() + parsed.slice(1).replace(/([A-Z])/g," $1")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a randomized color string.
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function getRandomColor() {
|
||||||
|
let clrs = '0123456789ABCDEF';
|
||||||
|
let color = '#';
|
||||||
|
for(let i = 0; i < 6; i++) {
|
||||||
|
color += clrs[Math.floor(Math.random() * (16-5*(i%2===0)))];
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes text to html entities.
|
||||||
|
* @param {string} str
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function escapeHTML(str) {
|
||||||
|
return str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>') ;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses exponents and replaces them with expression values
|
||||||
|
* @param {string} expression - The expression to replace in.
|
||||||
|
* @return {string} The parsed expression
|
||||||
|
*/
|
||||||
|
export function exponentsToExpression(expression) {
|
||||||
|
return expression.replace(exponentReg, (m, exp) => '^' + exp.split('').map((x) => exponents.indexOf(x)).join(''))
|
||||||
|
}
|
|
@ -1,258 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { textsub, textsup } from "./subsup.mjs"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simplifies (mathematically) a mathematical expression.
|
|
||||||
* @deprecated
|
|
||||||
* @param {string} str - Expression to parse
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
export function simplifyExpression(str) {
|
|
||||||
let replacements = [
|
|
||||||
// Operations not done by parser.
|
|
||||||
// [// Decomposition way 2
|
|
||||||
// /(^|[+-] |\()([-.\d\w]+) ([*/]) \((([-.\d\w] [*/] )?[-\d\w.]+) ([+\-]) (([-.\d\w] [*/] )?[\d\w.+]+)\)($| [+-]|\))/g,
|
|
||||||
// "$1$2 $3 $4 $6 $2 $3 $7$9"
|
|
||||||
// ],
|
|
||||||
// [ // Decomposition way 2
|
|
||||||
// /(^|[+-] |\()\((([-.\d\w] [*/] )?[-\d\w.]+) ([+\-]) (([-.\d\w] [*/] )?[\d\w.+]+)\) ([*/]) ([-.\d\w]+)($| [+-]|\))/g,
|
|
||||||
// "$1$2 $7 $8 $4 $5 $7 $8$9"
|
|
||||||
// ],
|
|
||||||
[ // Factorisation of π elements.
|
|
||||||
/(([-\d\w.]+ [*/] )*)(pi|π)(( [/*] [-\d\w.]+)*) ([+-]) (([-\d\w.]+ [*/] )*)(pi|π)(( [/*] [-\d\w.]+)*)?/g,
|
|
||||||
function(match, m1, n1, pi1, m2, ope2, n2, opeM, m3, n3, pi2, m4, ope4, n4) {
|
|
||||||
// g1, g2, g3 , g4, g5, g6, g7, g8, g9, g10, g11,g12 , g13
|
|
||||||
// We don't care about mx & pix, ope2 & ope4 are either / or * for n2 & n4.
|
|
||||||
// n1 & n3 are multiplied, opeM is the main operation (- or +).
|
|
||||||
// Putting all n in form of number
|
|
||||||
//n2 = n2 == undefined ? 1 : parseFloat(n)
|
|
||||||
n1 = m1 === undefined ? 1 : eval(m1 + "1")
|
|
||||||
n2 = m2 === undefined ? 1 : eval("1" + m2)
|
|
||||||
n3 = m3 === undefined ? 1 : eval(m3 + "1")
|
|
||||||
n4 = m4 === undefined ? 1 : eval("1" + m4)
|
|
||||||
//let [n1, n2, n3, n4] = [n1, n2, n3, n4].map(n => n == undefined ? 1 : parseFloat(n))
|
|
||||||
// Falling back to * in case it does not exist (the corresponding n would be 1)
|
|
||||||
[ope2, ope4] = [ope2, ope4].map(ope => ope === "/" ? "/" : "*")
|
|
||||||
let coeff1 = n1 * n2
|
|
||||||
let coeff2 = n3 * n4
|
|
||||||
let coefficient = coeff1 + coeff2 - (opeM === "-" ? 2 * coeff2 : 0)
|
|
||||||
|
|
||||||
return `${coefficient} * π`
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[ // Removing parenthesis when content is only added from both sides.
|
|
||||||
/(^|[+-] |\()\(([^)(]+)\)($| [+-]|\))/g,
|
|
||||||
function(match, b4, middle, after) {
|
|
||||||
return `${b4}${middle}${after}`
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[ // Removing parenthesis when content is only multiplied.
|
|
||||||
/(^|[*\/] |\()\(([^)(+-]+)\)($| [*\/+-]|\))/g,
|
|
||||||
function(match, b4, middle, after) {
|
|
||||||
return `${b4}${middle}${after}`
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[ // Removing parenthesis when content is only multiplied.
|
|
||||||
/(^|[*\/+-] |\()\(([^)(+-]+)\)($| [*\/]|\))/g,
|
|
||||||
function(match, b4, middle, after) {
|
|
||||||
return `${b4}${middle}${after}`
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[// Simplification additions/subtractions.
|
|
||||||
/(^|[^*\/] |\()([-.\d]+) [+-] (\([^)(]+\)|[^)(]+) [+-] ([-.\d]+)($| [^*\/]|\))/g,
|
|
||||||
function(match, b4, n1, op1, middle, op2, n2, after) {
|
|
||||||
let total
|
|
||||||
if(op2 === "+") {
|
|
||||||
total = parseFloat(n1) + parseFloat(n2)
|
|
||||||
} else {
|
|
||||||
total = parseFloat(n1) - parseFloat(n2)
|
|
||||||
}
|
|
||||||
return `${b4}${total} ${op1} ${middle}${after}`
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[// Simplification multiplications/divisions.
|
|
||||||
/([-.\d]+) [*\/] (\([^)(]+\)|[^)(+-]+) [*\/] ([-.\d]+)/g,
|
|
||||||
function(match, n1, op1, middle, op2, n2) {
|
|
||||||
if(parseInt(n1) === n1 && parseInt(n2) === n2 && op2 === "/" &&
|
|
||||||
(parseInt(n1) / parseInt(n2)) % 1 !== 0) {
|
|
||||||
// Non int result for int division.
|
|
||||||
return `(${n1} / ${n2}) ${op1} ${middle}`
|
|
||||||
} else {
|
|
||||||
if(op2 === "*") {
|
|
||||||
return `${parseFloat(n1) * parseFloat(n2)} ${op1} ${middle}`
|
|
||||||
} else {
|
|
||||||
return `${parseFloat(n1) / parseFloat(n2)} ${op1} ${middle}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[// Starting & ending parenthesis if not needed.
|
|
||||||
/^\s*\((.*)\)\s*$/g,
|
|
||||||
function(match, middle) {
|
|
||||||
let str = middle
|
|
||||||
// Replace all groups
|
|
||||||
while(/\([^)(]+\)/g.test(str))
|
|
||||||
str = str.replace(/\([^)(]+\)/g, "")
|
|
||||||
// There shouldn't be any more parenthesis
|
|
||||||
// If there is, that means the 2 parenthesis are needed.
|
|
||||||
if(!str.includes(")") && !str.includes("(")) {
|
|
||||||
return middle
|
|
||||||
} else {
|
|
||||||
return `(${middle})`
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
]
|
|
||||||
// Simple simplifications
|
|
||||||
// [/(\s|^|\()0(\.0+)? \* (\([^)(]+\))/g, '$10'],
|
|
||||||
// [/(\s|^|\()0(\.0+)? \* ([^)(+-]+)/g, '$10'],
|
|
||||||
// [/(\([^)(]\)) \* 0(\.0+)?(\s|$|\))/g, '0$3'],
|
|
||||||
// [/([^)(+-]) \* 0(\.0+)?(\s|$|\))/g, '0$3'],
|
|
||||||
// [/(\s|^|\()1(\.0+)? [\*\/] /g, '$1'],
|
|
||||||
// [/(\s|^|\()0(\.0+)? (\+|\-) /g, '$1'],
|
|
||||||
// [/ [\*\/] 1(\.0+)?(\s|$|\))/g, '$3'],
|
|
||||||
// [/ (\+|\-) 0(\.0+)?(\s|$|\))/g, '$3'],
|
|
||||||
// [/(^| |\() /g, '$1'],
|
|
||||||
// [/ ($|\))/g, '$1'],
|
|
||||||
]
|
|
||||||
|
|
||||||
// Replacements
|
|
||||||
let found
|
|
||||||
do {
|
|
||||||
found = false
|
|
||||||
for(let replacement of replacements)
|
|
||||||
while(replacement[0].test(str)) {
|
|
||||||
found = true
|
|
||||||
str = str.replace(replacement[0], replacement[1])
|
|
||||||
}
|
|
||||||
} while(found)
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transforms a mathematical expression to make it readable by humans.
|
|
||||||
* NOTE: Will break parsing of expression.
|
|
||||||
* @deprecated
|
|
||||||
* @param {string} str - Expression to parse.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
export function makeExpressionReadable(str) {
|
|
||||||
let replacements = [
|
|
||||||
// letiables
|
|
||||||
[/pi/g, "π"],
|
|
||||||
[/Infinity/g, "∞"],
|
|
||||||
[/inf/g, "∞"],
|
|
||||||
// Other
|
|
||||||
[/ \* /g, "×"],
|
|
||||||
[/ \^ /g, "^"],
|
|
||||||
[/\^\(([\d\w+-]+)\)/g, function(match, p1) {
|
|
||||||
return textsup(p1)
|
|
||||||
}],
|
|
||||||
[/\^([\d\w+-]+)/g, function(match, p1) {
|
|
||||||
return textsup(p1)
|
|
||||||
}],
|
|
||||||
[/_\(([\d\w+-]+)\)/g, function(match, p1) {
|
|
||||||
return textsub(p1)
|
|
||||||
}],
|
|
||||||
[/_([\d\w+-]+)/g, function(match, p1) {
|
|
||||||
return textsub(p1)
|
|
||||||
}],
|
|
||||||
[/\[([^\[\]]+)\]/g, function(match, p1) {
|
|
||||||
return textsub(p1)
|
|
||||||
}],
|
|
||||||
[/(\d|\))×/g, "$1"],
|
|
||||||
[/integral\((.+),\s?(.+),\s?["'](.+)["'],\s?["'](.+)["']\)/g, function(match, a, b, p1, body, p2, p3, by, p4) {
|
|
||||||
if(a.length < b.length) {
|
|
||||||
return `∫${textsub(a)}${textsup(b)} ${body} d${by}`
|
|
||||||
} else {
|
|
||||||
return `∫${textsup(b)}${textsub(a)} ${body} d${by}`
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
[/derivative\(?["'](.+)["'], ?["'](.+)["'], ?(.+)\)?/g, function(match, p1, body, p2, p3, by, p4, x) {
|
|
||||||
return `d(${body.replace(new RegExp(by, "g"), "x")})/dx`
|
|
||||||
}]
|
|
||||||
]
|
|
||||||
|
|
||||||
// str = simplifyExpression(str)
|
|
||||||
// Replacements
|
|
||||||
for(let replacement of replacements)
|
|
||||||
while(replacement[0].test(str))
|
|
||||||
str = str.replace(replacement[0], replacement[1])
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @type {[RegExp, string][]} */
|
|
||||||
const replacements = [
|
|
||||||
// Greek letters
|
|
||||||
[/(\W|^)al(pha)?(\W|$)/g, "$1α$3"],
|
|
||||||
[/(\W|^)be(ta)?(\W|$)/g, "$1β$3"],
|
|
||||||
[/(\W|^)ga(mma)?(\W|$)/g, "$1γ$3"],
|
|
||||||
[/(\W|^)de(lta)?(\W|$)/g, "$1δ$3"],
|
|
||||||
[/(\W|^)ep(silon)?(\W|$)/g, "$1ε$3"],
|
|
||||||
[/(\W|^)ze(ta)?(\W|$)/g, "$1ζ$3"],
|
|
||||||
[/(\W|^)et(a)?(\W|$)/g, "$1η$3"],
|
|
||||||
[/(\W|^)th(eta)?(\W|$)/g, "$1θ$3"],
|
|
||||||
[/(\W|^)io(ta)?(\W|$)/g, "$1ι$3"],
|
|
||||||
[/(\W|^)ka(ppa)?(\W|$)/g, "$1κ$3"],
|
|
||||||
[/(\W|^)la(mbda)?(\W|$)/g, "$1λ$3"],
|
|
||||||
[/(\W|^)mu(\W|$)/g, "$1μ$2"],
|
|
||||||
[/(\W|^)nu(\W|$)/g, "$1ν$2"],
|
|
||||||
[/(\W|^)xi(\W|$)/g, "$1ξ$2"],
|
|
||||||
[/(\W|^)rh(o)?(\W|$)/g, "$1ρ$3"],
|
|
||||||
[/(\W|^)si(gma)?(\W|$)/g, "$1σ$3"],
|
|
||||||
[/(\W|^)ta(u)?(\W|$)/g, "$1τ$3"],
|
|
||||||
[/(\W|^)up(silon)?(\W|$)/g, "$1υ$3"],
|
|
||||||
[/(\W|^)ph(i)?(\W|$)/g, "$1φ$3"],
|
|
||||||
[/(\W|^)ch(i)?(\W|$)/g, "$1χ$3"],
|
|
||||||
[/(\W|^)ps(i)?(\W|$)/g, "$1ψ$3"],
|
|
||||||
[/(\W|^)om(ega)?(\W|$)/g, "$1ω$3"],
|
|
||||||
// Capital greek letters
|
|
||||||
[/(\W|^)gga(mma)?(\W|$)/g, "$1Γ$3"],
|
|
||||||
[/(\W|^)gde(lta)?(\W|$)/g, "$1Δ$3"],
|
|
||||||
[/(\W|^)gth(eta)?(\W|$)/g, "$1Θ$3"],
|
|
||||||
[/(\W|^)gla(mbda)?(\W|$)/g, "$1Λ$3"],
|
|
||||||
[/(\W|^)gxi(\W|$)/g, "$1Ξ$2"],
|
|
||||||
[/(\W|^)gpi(\W|$)/g, "$1Π$2"],
|
|
||||||
[/(\W|^)gsi(gma)?(\W|$)/g, "$1Σ$3"],
|
|
||||||
[/(\W|^)gph(i)?(\W|$)/g, "$1Φ$3"],
|
|
||||||
[/(\W|^)gps(i)?(\W|$)/g, "$1Ψ$3"],
|
|
||||||
[/(\W|^)gom(ega)?(\W|$)/g, "$1Ω$3"],
|
|
||||||
// Array elements
|
|
||||||
[/\[([^\]\[]+)\]/g, function(match, p1) {
|
|
||||||
return textsub(p1)
|
|
||||||
}]
|
|
||||||
]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a variable name to make it readable by humans.
|
|
||||||
*
|
|
||||||
* @param {string} str - Variable name to parse
|
|
||||||
* @param {boolean} removeUnallowed - Remove domain symbols disallowed in name.
|
|
||||||
* @returns {string} - The parsed name
|
|
||||||
*/
|
|
||||||
export function parseName(str, removeUnallowed = true) {
|
|
||||||
for(const replacement of replacements)
|
|
||||||
str = str.replace(replacement[0], replacement[1])
|
|
||||||
if(removeUnallowed)
|
|
||||||
str = str.replace(/[xnπℝℕ\\∪∩\]\[ ()^/÷*×+=\d¹²³⁴⁵⁶⁷⁸⁹⁰-]/g, "")
|
|
||||||
|
|
||||||
return str
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export * from "./prototype.mjs"
|
|
||||||
export * from "./subsup.mjs"
|
|
||||||
export * from "./expression.mjs"
|
|
||||||
export * from "./other.mjs"
|
|
|
@ -1,41 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a randomized color string.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
export function getRandomColor() {
|
|
||||||
let clrs = "0123456789ABCDEF"
|
|
||||||
let color = "#"
|
|
||||||
for(let i = 0; i < 6; i++) {
|
|
||||||
color += clrs[Math.floor(Math.random() * (16 - 5 * (i % 2 === 0)))]
|
|
||||||
}
|
|
||||||
return color
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Escapes text to html entities.
|
|
||||||
* @param {string} str
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
export function escapeHTML(str) {
|
|
||||||
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Add string methods
|
|
||||||
/**
|
|
||||||
* Replaces latin characters with their uppercase versions.
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
String.prototype.toLatinUppercase = function() {
|
|
||||||
return this.replace(/[a-z]/g, function(match) {
|
|
||||||
return match.toUpperCase()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the first and last character of a string
|
|
||||||
* Used to remove enclosing characters like quotes, parentheses, brackets...
|
|
||||||
* @note Does NOT check for their existence ahead of time.
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
String.prototype.removeEnclosure = function() {
|
|
||||||
return this.substring(1, this.length - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rounds to a certain number of decimal places.
|
|
||||||
* From https://stackoverflow.com/a/48764436
|
|
||||||
*
|
|
||||||
* @param {number} decimalPlaces
|
|
||||||
* @return {number}
|
|
||||||
*/
|
|
||||||
Number.prototype.toDecimalPrecision = function(decimalPlaces = 0) {
|
|
||||||
const p = Math.pow(10, decimalPlaces)
|
|
||||||
const n = (this * p) * (1 + Number.EPSILON)
|
|
||||||
return Math.round(n) / p
|
|
||||||
}
|
|
|
@ -1,140 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const CHARACTER_TO_POWER = new Map([
|
|
||||||
["-", "⁻"],
|
|
||||||
["+", "⁺"],
|
|
||||||
["=", "⁼"],
|
|
||||||
[" ", " "],
|
|
||||||
["(", "⁽"],
|
|
||||||
[")", "⁾"],
|
|
||||||
["0", "⁰"],
|
|
||||||
["1", "¹"],
|
|
||||||
["2", "²"],
|
|
||||||
["3", "³"],
|
|
||||||
["4", "⁴"],
|
|
||||||
["5", "⁵"],
|
|
||||||
["6", "⁶"],
|
|
||||||
["7", "⁷"],
|
|
||||||
["8", "⁸"],
|
|
||||||
["9", "⁹"],
|
|
||||||
["a", "ᵃ"],
|
|
||||||
["b", "ᵇ"],
|
|
||||||
["c", "ᶜ"],
|
|
||||||
["d", "ᵈ"],
|
|
||||||
["e", "ᵉ"],
|
|
||||||
["f", "ᶠ"],
|
|
||||||
["g", "ᵍ"],
|
|
||||||
["h", "ʰ"],
|
|
||||||
["i", "ⁱ"],
|
|
||||||
["j", "ʲ"],
|
|
||||||
["k", "ᵏ"],
|
|
||||||
["l", "ˡ"],
|
|
||||||
["m", "ᵐ"],
|
|
||||||
["n", "ⁿ"],
|
|
||||||
["o", "ᵒ"],
|
|
||||||
["p", "ᵖ"],
|
|
||||||
["r", "ʳ"],
|
|
||||||
["s", "ˢ"],
|
|
||||||
["t", "ᵗ"],
|
|
||||||
["u", "ᵘ"],
|
|
||||||
["v", "ᵛ"],
|
|
||||||
["w", "ʷ"],
|
|
||||||
["x", "ˣ"],
|
|
||||||
["y", "ʸ"],
|
|
||||||
["z", "ᶻ"]
|
|
||||||
])
|
|
||||||
|
|
||||||
const CHARACTER_TO_INDICE = new Map([
|
|
||||||
["-", "₋"],
|
|
||||||
["+", "₊"],
|
|
||||||
["=", "₌"],
|
|
||||||
["(", "₍"],
|
|
||||||
[")", "₎"],
|
|
||||||
[" ", " "],
|
|
||||||
["0", "₀"],
|
|
||||||
["1", "₁"],
|
|
||||||
["2", "₂"],
|
|
||||||
["3", "₃"],
|
|
||||||
["4", "₄"],
|
|
||||||
["5", "₅"],
|
|
||||||
["6", "₆"],
|
|
||||||
["7", "₇"],
|
|
||||||
["8", "₈"],
|
|
||||||
["9", "₉"],
|
|
||||||
["a", "ₐ"],
|
|
||||||
["e", "ₑ"],
|
|
||||||
["h", "ₕ"],
|
|
||||||
["i", "ᵢ"],
|
|
||||||
["j", "ⱼ"],
|
|
||||||
["k", "ₖ"],
|
|
||||||
["l", "ₗ"],
|
|
||||||
["m", "ₘ"],
|
|
||||||
["n", "ₙ"],
|
|
||||||
["o", "ₒ"],
|
|
||||||
["p", "ₚ"],
|
|
||||||
["r", "ᵣ"],
|
|
||||||
["s", "ₛ"],
|
|
||||||
["t", "ₜ"],
|
|
||||||
["u", "ᵤ"],
|
|
||||||
["v", "ᵥ"],
|
|
||||||
["x", "ₓ"]
|
|
||||||
])
|
|
||||||
|
|
||||||
const EXPONENTS = [
|
|
||||||
"⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹"
|
|
||||||
]
|
|
||||||
|
|
||||||
const EXPONENTS_REG = new RegExp("([" + EXPONENTS.join("") + "]+)", "g")
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Put a text in sup position
|
|
||||||
* @param {string} text
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
export function textsup(text) {
|
|
||||||
let ret = ""
|
|
||||||
text = text.toString()
|
|
||||||
for(let letter of text)
|
|
||||||
ret += CHARACTER_TO_POWER.has(letter) ? CHARACTER_TO_POWER.get(letter) : letter
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Put a text in sub position
|
|
||||||
* @param {string} text
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
export function textsub(text) {
|
|
||||||
let ret = ""
|
|
||||||
text = text.toString()
|
|
||||||
for(let letter of text)
|
|
||||||
ret += CHARACTER_TO_INDICE.has(letter) ? CHARACTER_TO_INDICE.get(letter) : letter
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses exponents and replaces them with expression values
|
|
||||||
* @param {string} expression - The expression to replace in.
|
|
||||||
* @return {string} The parsed expression
|
|
||||||
*/
|
|
||||||
export function exponentsToExpression(expression) {
|
|
||||||
return expression.replace(EXPONENTS_REG, (m, exp) => "^" + exp.split("").map((x) => EXPONENTS.indexOf(x)).join(""))
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,118 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { describe, it } from "mocha"
|
|
||||||
import { expect } from "chai"
|
|
||||||
|
|
||||||
const { spy } = chaiPlugins
|
|
||||||
|
|
||||||
import { BaseEventEmitter, BaseEvent } from "../../src/events.mjs"
|
|
||||||
|
|
||||||
class MockEmitter extends BaseEventEmitter {
|
|
||||||
static emits = ["example1", "example2"]
|
|
||||||
}
|
|
||||||
|
|
||||||
class MockEvent1 extends BaseEvent {
|
|
||||||
constructor() {
|
|
||||||
super("example1")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MockEvent2 extends BaseEvent {
|
|
||||||
constructor(parameter) {
|
|
||||||
super("example2")
|
|
||||||
this.parameter = parameter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("Lib/EventsEmitters", function() {
|
|
||||||
it("sends events with unique and readonly names", function() {
|
|
||||||
const event = new MockEvent1()
|
|
||||||
expect(event.name).to.equal("example1")
|
|
||||||
expect(() => event.name = "not").to.throw()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("forwards events to all of its listeners", function() {
|
|
||||||
const emitter = new MockEmitter()
|
|
||||||
const listener1 = spy()
|
|
||||||
const listener2 = spy()
|
|
||||||
emitter.on("example1", listener1)
|
|
||||||
emitter.on("example1", listener2)
|
|
||||||
emitter.emit(new MockEvent1())
|
|
||||||
expect(listener1).to.have.been.called.once
|
|
||||||
expect(listener2).to.have.been.called.once
|
|
||||||
})
|
|
||||||
|
|
||||||
it("forwards multiple events to a singular listener", function() {
|
|
||||||
const emitter = new MockEmitter()
|
|
||||||
const listener = spy()
|
|
||||||
const mockEvent1 = new MockEvent1()
|
|
||||||
const mockEvent2 = new MockEvent2(3)
|
|
||||||
emitter.on("example1 example2", listener)
|
|
||||||
emitter.emit(mockEvent1)
|
|
||||||
emitter.emit(mockEvent2)
|
|
||||||
expect(listener).to.have.been.called.twice
|
|
||||||
expect(listener).to.have.been.first.called.with.exactly(mockEvent1)
|
|
||||||
expect(listener).to.have.been.second.called.with.exactly(mockEvent2)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("is able to have listeners removed", function() {
|
|
||||||
const emitter = new MockEmitter()
|
|
||||||
const listener = spy()
|
|
||||||
emitter.on("example1", listener)
|
|
||||||
const removedFromEventItDoesntListenTo = emitter.off("example2", listener)
|
|
||||||
const removedFromEventItListensTo = emitter.off("example1", listener)
|
|
||||||
const removedFromEventASecondTime = emitter.off("example1", listener)
|
|
||||||
expect(removedFromEventItDoesntListenTo).to.be.false
|
|
||||||
expect(removedFromEventItListensTo).to.be.true
|
|
||||||
expect(removedFromEventASecondTime).to.be.false
|
|
||||||
emitter.on("example1 example2", listener)
|
|
||||||
const removedFromBothEvents = emitter.off("example1 example2", listener)
|
|
||||||
expect(removedFromBothEvents).to.be.true
|
|
||||||
emitter.on("example1", listener)
|
|
||||||
const removedFromOneOfTheEvents = emitter.off("example1 example2", listener)
|
|
||||||
expect(removedFromOneOfTheEvents).to.be.true
|
|
||||||
})
|
|
||||||
|
|
||||||
it("is able to have one listener's listening to a single event removed when said listener listens to multiple", function() {
|
|
||||||
const emitter = new MockEmitter()
|
|
||||||
const listener = spy()
|
|
||||||
const mockEvent1 = new MockEvent1()
|
|
||||||
const mockEvent2 = new MockEvent2(3)
|
|
||||||
emitter.on("example1 example2", listener)
|
|
||||||
// Disable listener for example1
|
|
||||||
emitter.off("example1", listener)
|
|
||||||
emitter.emit(mockEvent1)
|
|
||||||
emitter.emit(mockEvent2)
|
|
||||||
expect(listener).to.have.been.called.once
|
|
||||||
expect(listener).to.also.have.been.called.with.exactly(mockEvent2)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("is not able to emit or add/remove listeners for inexistant events", function() {
|
|
||||||
const emitter = new MockEmitter()
|
|
||||||
const listener = spy()
|
|
||||||
expect(() => emitter.on("inexistant", listener)).to.throw(Error)
|
|
||||||
expect(() => emitter.off("inexistant", listener)).to.throw(Error)
|
|
||||||
expect(() => emitter.emit(new BaseEvent("inexistant"))).to.throw(Error)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("isn't able to emit non-events", function() {
|
|
||||||
const emitter = new MockEmitter()
|
|
||||||
expect(() => emitter.emit("not-an-event")).to.throw(Error)
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,58 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { describe, it } from "mocha"
|
|
||||||
import { expect } from "chai"
|
|
||||||
|
|
||||||
import { MockLatex } from "../mock/latex.mjs"
|
|
||||||
import { MockHelper } from "../mock/helper.mjs"
|
|
||||||
import {
|
|
||||||
CanvasInterface,
|
|
||||||
DialogInterface,
|
|
||||||
HelperInterface,
|
|
||||||
Interface,
|
|
||||||
LatexInterface,
|
|
||||||
RootInterface
|
|
||||||
} from "../../src/module/interface.mjs"
|
|
||||||
import { MockDialog } from "../mock/dialog.mjs"
|
|
||||||
import { MockRootElement } from "../mock/root.mjs"
|
|
||||||
import { MockCanvas } from "../mock/canvas.mjs"
|
|
||||||
|
|
||||||
describe("Interfaces", function() {
|
|
||||||
describe("#interface methods", function() {
|
|
||||||
it("throws an error when called directly", function() {
|
|
||||||
const obj = new CanvasInterface()
|
|
||||||
expect(() => obj.markDirty()).to.throw(Error)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#checkImplementation", function() {
|
|
||||||
it("validates the implementation of mocks", function() {
|
|
||||||
const checkMockLatex = () => Interface.checkImplementation(LatexInterface, new MockLatex())
|
|
||||||
const checkMockHelper = () => Interface.checkImplementation(HelperInterface, new MockHelper())
|
|
||||||
const checkMockDialog = () => Interface.checkImplementation(DialogInterface, new MockDialog())
|
|
||||||
const checkMockRoot = () => Interface.checkImplementation(RootInterface, new MockRootElement())
|
|
||||||
const checkMockCanvas = () => Interface.checkImplementation(CanvasInterface, new MockCanvas())
|
|
||||||
expect(checkMockLatex).to.not.throw()
|
|
||||||
expect(checkMockHelper).to.not.throw()
|
|
||||||
expect(checkMockDialog).to.not.throw()
|
|
||||||
expect(checkMockRoot).to.not.throw()
|
|
||||||
expect(checkMockCanvas).to.not.throw()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,232 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Load prior tests
|
|
||||||
|
|
||||||
import { describe, it } from "mocha"
|
|
||||||
import { expect } from "chai"
|
|
||||||
|
|
||||||
import * as Polyfill from "../../src/lib/expr-eval/polyfill.mjs"
|
|
||||||
import {
|
|
||||||
andOperator,
|
|
||||||
cbrt,
|
|
||||||
equal,
|
|
||||||
expm1,
|
|
||||||
hypot,
|
|
||||||
lessThan,
|
|
||||||
log1p,
|
|
||||||
log2,
|
|
||||||
notEqual
|
|
||||||
} from "../../src/lib/expr-eval/polyfill.mjs"
|
|
||||||
|
|
||||||
describe("Math/Polyfill", () => {
|
|
||||||
describe("#add", function() {
|
|
||||||
it("adds two numbers", function() {
|
|
||||||
expect(Polyfill.add(2, 3)).to.equal(5)
|
|
||||||
expect(Polyfill.add("2", "3")).to.equal(5)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#sub", function() {
|
|
||||||
it("subtracts two numbers", function() {
|
|
||||||
expect(Polyfill.sub(2, 1)).to.equal(1)
|
|
||||||
expect(Polyfill.sub("2", "1")).to.equal(1)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#mul", function() {
|
|
||||||
it("multiplies two numbers", function() {
|
|
||||||
expect(Polyfill.mul(2, 3)).to.equal(6)
|
|
||||||
expect(Polyfill.mul("2", "3")).to.equal(6)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#div", function() {
|
|
||||||
it("divides two numbers", function() {
|
|
||||||
expect(Polyfill.div(10, 2)).to.equal(5)
|
|
||||||
expect(Polyfill.div("10", "2")).to.equal(5)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#mod", function() {
|
|
||||||
it("returns the modulo of two numbers", function() {
|
|
||||||
expect(Polyfill.mod(10, 3)).to.equal(1)
|
|
||||||
expect(Polyfill.mod("10", "3")).to.equal(1)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#concat", function() {
|
|
||||||
it("returns the concatenation of two strings", function() {
|
|
||||||
expect(Polyfill.concat(10, 3)).to.equal("103")
|
|
||||||
expect(Polyfill.concat("abc", "def")).to.equal("abcdef")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#equal", function() {
|
|
||||||
it("returns whether its two arguments are equal", function() {
|
|
||||||
expect(Polyfill.equal(10, 3)).to.be.false
|
|
||||||
expect(Polyfill.equal(10, 10)).to.be.true
|
|
||||||
expect(Polyfill.equal("abc", "def")).to.be.false
|
|
||||||
expect(Polyfill.equal("abc", "abc")).to.be.true
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#notEqual", function() {
|
|
||||||
it("returns whether its two arguments are not equal", function() {
|
|
||||||
expect(Polyfill.notEqual(10, 3)).to.be.true
|
|
||||||
expect(Polyfill.notEqual(10, 10)).to.be.false
|
|
||||||
expect(Polyfill.notEqual("abc", "def")).to.be.true
|
|
||||||
expect(Polyfill.notEqual("abc", "abc")).to.be.false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#greaterThan", function() {
|
|
||||||
it("returns whether its first argument is strictly greater than its second", function() {
|
|
||||||
expect(Polyfill.greaterThan(10, 3)).to.be.true
|
|
||||||
expect(Polyfill.greaterThan(10, 10)).to.be.false
|
|
||||||
expect(Polyfill.greaterThan(10, 30)).to.be.false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#lessThan", function() {
|
|
||||||
it("returns whether its first argument is strictly less than its second", function() {
|
|
||||||
expect(Polyfill.lessThan(10, 3)).to.be.false
|
|
||||||
expect(Polyfill.lessThan(10, 10)).to.be.false
|
|
||||||
expect(Polyfill.lessThan(10, 30)).to.be.true
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#greaterThanEqual", function() {
|
|
||||||
it("returns whether its first argument is greater or equal to its second", function() {
|
|
||||||
expect(Polyfill.greaterThanEqual(10, 3)).to.be.true
|
|
||||||
expect(Polyfill.greaterThanEqual(10, 10)).to.be.true
|
|
||||||
expect(Polyfill.greaterThanEqual(10, 30)).to.be.false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#lessThanEqual", function() {
|
|
||||||
it("returns whether its first argument is strictly less than its second", function() {
|
|
||||||
expect(Polyfill.lessThanEqual(10, 3)).to.be.false
|
|
||||||
expect(Polyfill.lessThanEqual(10, 10)).to.be.true
|
|
||||||
expect(Polyfill.lessThanEqual(10, 30)).to.be.true
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#andOperator", function() {
|
|
||||||
it("returns whether its arguments are both true", function() {
|
|
||||||
expect(Polyfill.andOperator(true, true)).to.be.true
|
|
||||||
expect(Polyfill.andOperator(true, false)).to.be.false
|
|
||||||
expect(Polyfill.andOperator(false, true)).to.be.false
|
|
||||||
expect(Polyfill.andOperator(false, false)).to.be.false
|
|
||||||
expect(Polyfill.andOperator(10, 3)).to.be.true
|
|
||||||
expect(Polyfill.andOperator(10, 0)).to.be.false
|
|
||||||
expect(Polyfill.andOperator(0, 0)).to.be.false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#orOperator", function() {
|
|
||||||
it("returns whether one of its arguments is true", function() {
|
|
||||||
expect(Polyfill.orOperator(true, true)).to.be.true
|
|
||||||
expect(Polyfill.orOperator(true, false)).to.be.true
|
|
||||||
expect(Polyfill.orOperator(false, true)).to.be.true
|
|
||||||
expect(Polyfill.orOperator(false, false)).to.be.false
|
|
||||||
expect(Polyfill.orOperator(10, 3)).to.be.true
|
|
||||||
expect(Polyfill.orOperator(10, 0)).to.be.true
|
|
||||||
expect(Polyfill.orOperator(0, 0)).to.be.false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#inOperator", function() {
|
|
||||||
it("checks if second argument contains first", function() {
|
|
||||||
expect(Polyfill.inOperator("a", ["a", "b", "c"])).to.be.true
|
|
||||||
expect(Polyfill.inOperator(3, [0, 1, 2])).to.be.false
|
|
||||||
expect(Polyfill.inOperator(3, [0, 1, 3, 2])).to.be.true
|
|
||||||
expect(Polyfill.inOperator("a", "abcdef")).to.be.true
|
|
||||||
expect(Polyfill.inOperator("a", "bcdefg")).to.be.false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#sinh, #cosh, #tanh, #asinh, #acosh, #atanh", function() {
|
|
||||||
const EPSILON = 1e-12
|
|
||||||
for(let x = -.9; x < 1; x += 0.1) {
|
|
||||||
expect(Polyfill.sinh(x)).to.be.approximately(Math.sinh(x), EPSILON)
|
|
||||||
expect(Polyfill.cosh(x)).to.be.approximately(Math.cosh(x), EPSILON)
|
|
||||||
expect(Polyfill.tanh(x)).to.be.approximately(Math.tanh(x), EPSILON)
|
|
||||||
expect(Polyfill.asinh(x)).to.be.approximately(Math.asinh(x), EPSILON)
|
|
||||||
expect(Polyfill.atanh(x)).to.be.approximately(Math.atanh(x), EPSILON)
|
|
||||||
}
|
|
||||||
for(let x = 1.1; x < 10; x += 0.1) {
|
|
||||||
expect(Polyfill.sinh(x)).to.be.approximately(Math.sinh(x), EPSILON)
|
|
||||||
expect(Polyfill.cosh(x)).to.be.approximately(Math.cosh(x), EPSILON)
|
|
||||||
expect(Polyfill.tanh(x)).to.be.approximately(Math.tanh(x), EPSILON)
|
|
||||||
expect(Polyfill.asinh(x)).to.be.approximately(Math.asinh(x), EPSILON)
|
|
||||||
expect(Polyfill.acosh(x)).to.be.approximately(Math.acosh(x), EPSILON)
|
|
||||||
expect(Polyfill.log10(x)).to.be.approximately(Math.log10(x), EPSILON)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#trunc", function() {
|
|
||||||
it("returns the decimal part of floats", function() {
|
|
||||||
for(let x = -10; x < 10; x += 0.1)
|
|
||||||
expect(Polyfill.trunc(x)).to.equal(Math.trunc(x))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#gamma", function() {
|
|
||||||
it("returns the product of factorial(x - 1)", function() {
|
|
||||||
expect(Polyfill.gamma(0)).to.equal(Infinity)
|
|
||||||
expect(Polyfill.gamma(1)).to.equal(1)
|
|
||||||
expect(Polyfill.gamma(2)).to.equal(1)
|
|
||||||
expect(Polyfill.gamma(3)).to.equal(2)
|
|
||||||
expect(Polyfill.gamma(4)).to.equal(6)
|
|
||||||
expect(Polyfill.gamma(5)).to.equal(24)
|
|
||||||
expect(Polyfill.gamma(172)).to.equal(Infinity)
|
|
||||||
expect(Polyfill.gamma(172.3)).to.equal(Infinity)
|
|
||||||
expect(Polyfill.gamma(.2)).to.approximately(4.590_843_712, 1e-8)
|
|
||||||
expect(Polyfill.gamma(5.5)).to.be.approximately(52.34277778, 1e-8)
|
|
||||||
expect(Polyfill.gamma(90.2)).to.equal(4.0565358202825355e+136)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#hypot", function() {
|
|
||||||
it("returns the hypothenus length of a triangle whose length are provided in arguments", function() {
|
|
||||||
for(let x = 0; x < 10; x += 0.3) {
|
|
||||||
expect(Polyfill.hypot(x)).to.be.approximately(Math.hypot(x), Number.EPSILON)
|
|
||||||
for(let y = 0; y < 10; y += 0.3) {
|
|
||||||
expect(Polyfill.hypot(x, y)).to.be.approximately(Math.hypot(x, y), Number.EPSILON)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#sign, #cbrt, #exmp1", function() {
|
|
||||||
for(let x = -10; x < 10; x += 0.3) {
|
|
||||||
expect(Polyfill.sign(x)).to.approximately(Math.sign(x), 1e-12)
|
|
||||||
expect(Polyfill.cbrt(x)).to.approximately(Math.cbrt(x), 1e-12)
|
|
||||||
expect(Polyfill.expm1(x)).to.approximately(Math.expm1(x), 1e-12)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#log1p, #log2", function() {
|
|
||||||
for(let x = 1; x < 10; x += 0.3) {
|
|
||||||
expect(Polyfill.log1p(x)).to
|
|
||||||
.be.approximately(Math.log1p(x), 1e-12)
|
|
||||||
expect(Polyfill.log2(x)).to.be.approximately(Math.log2(x), 1e-12)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,183 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { textsup, textsub, parseName, getRandomColor, escapeHTML, exponentsToExpression } from "../../src/utils/index.mjs"
|
|
||||||
|
|
||||||
|
|
||||||
import { describe, it } from "mocha"
|
|
||||||
import { expect } from "chai"
|
|
||||||
|
|
||||||
describe("Lib/PrototypeExtensions", function() {
|
|
||||||
describe("#String.toLatinUppercase", function() {
|
|
||||||
it("transforms latin characters from strings to their uppercase version", function() {
|
|
||||||
expect("abc".toLatinUppercase()).to.equal("ABC")
|
|
||||||
expect("abCd".toLatinUppercase()).to.equal("ABCD")
|
|
||||||
expect("ab123cd456".toLatinUppercase()).to.equal("AB123CD456")
|
|
||||||
expect("ABC".toLatinUppercase()).to.equal("ABC")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("does not transform non latin characters to their uppercase version", function() {
|
|
||||||
expect("abαπ".toLatinUppercase()).to.equal("ABαπ")
|
|
||||||
expect("abαπ".toLatinUppercase()).to.not.equal("abαπ".toUpperCase())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#String.removeEnclosure", function() {
|
|
||||||
it("is able to remove the first and last characters", function() {
|
|
||||||
expect("[1+t]".removeEnclosure()).to.equal("1+t")
|
|
||||||
expect('"a+b+c*d"'.removeEnclosure()).to.equal("a+b+c*d")
|
|
||||||
expect("(pi/2)".removeEnclosure()).to.equal("pi/2")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#Number.toDecimalPrecision", function() {
|
|
||||||
it("rounds a number to a fixed decimal precision", function() {
|
|
||||||
expect(123.456789.toDecimalPrecision()).to.equal(123)
|
|
||||||
expect(123.456789.toDecimalPrecision(1)).to.equal(123.5)
|
|
||||||
expect(123.456789.toDecimalPrecision(2)).to.equal(123.46)
|
|
||||||
expect(123.456789.toDecimalPrecision(3)).to.equal(123.457)
|
|
||||||
expect(123.456789.toDecimalPrecision(4)).to.equal(123.4568)
|
|
||||||
expect(123.456789.toDecimalPrecision(5)).to.equal(123.45679)
|
|
||||||
expect(123.456789.toDecimalPrecision(6)).to.equal(123.456789)
|
|
||||||
expect(123.111111.toDecimalPrecision(5)).to.equal(123.11111)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("Lib/Utils", function() {
|
|
||||||
describe("#textsup", function() {
|
|
||||||
it("transforms characters which have a sup unicode equivalent", function() {
|
|
||||||
expect(textsup("-+=()")).to.equal("⁻⁺⁼⁽⁾")
|
|
||||||
expect(textsup("0123456789")).to.equal("⁰¹²³⁴⁵⁶⁷⁸⁹")
|
|
||||||
expect(textsup("abcdefghijklmnoprstuvwxyz")).to.equal("ᵃᵇᶜᵈᵉᶠᵍʰⁱʲᵏˡᵐⁿᵒᵖʳˢᵗᵘᵛʷˣʸᶻ")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("does not transform characters without a sup equivalent", function() {
|
|
||||||
expect(textsup("ABCDEFGHIJKLMNOPQRSTUVWXYZq")).to.equal("ABCDEFGHIJKLMNOPQRSTUVWXYZq")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("partially transforms strings which only have a few characters with a sup equivalent", function() {
|
|
||||||
expect(textsup("ABCabcABC")).to.equal("ABCᵃᵇᶜABC")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#textsub", function() {
|
|
||||||
it("transforms characters which have a sub unicode equivalent", function() {
|
|
||||||
expect(textsub("-+=()")).to.equal("₋₊₌₍₎")
|
|
||||||
expect(textsub("0123456789")).to.equal("₀₁₂₃₄₅₆₇₈₉")
|
|
||||||
expect(textsub("aehijklmnoprstuvx")).to.equal("ₐₑₕᵢⱼₖₗₘₙₒₚᵣₛₜᵤᵥₓ")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("does not transform characters without a sub equivalent", function() {
|
|
||||||
expect(textsub("ABCDEFGHIJKLMNOPQRSTUVWXYZ")).to.equal("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
|
||||||
expect(textsub("bcdfgqyz")).to.equal("bcdfgqyz")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("partially transforms strings which only have a few characters with a sub equivalent", function() {
|
|
||||||
expect(textsub("ABC123ABC")).to.equal("ABC₁₂₃ABC")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#parseName", function() {
|
|
||||||
it("parses greek letter names", function() {
|
|
||||||
const shorthands = {
|
|
||||||
"α": ["al", "alpha"],
|
|
||||||
"β": ["be", "beta"],
|
|
||||||
"γ": ["ga", "gamma"],
|
|
||||||
"δ": ["de", "delta"],
|
|
||||||
"ε": ["ep", "epsilon"],
|
|
||||||
"ζ": ["ze", "zeta"],
|
|
||||||
"η": ["et", "eta"],
|
|
||||||
"θ": ["th", "theta"],
|
|
||||||
"κ": ["ka", "kappa"],
|
|
||||||
"λ": ["la", "lambda"],
|
|
||||||
"μ": ["mu"],
|
|
||||||
"ν": ["nu"],
|
|
||||||
"ξ": ["xi"],
|
|
||||||
"ρ": ["rh", "rho"],
|
|
||||||
"σ": ["si", "sigma"],
|
|
||||||
"τ": ["ta", "tau"],
|
|
||||||
"υ": ["up", "upsilon"],
|
|
||||||
"φ": ["ph", "phi"],
|
|
||||||
"χ": ["ch", "chi"],
|
|
||||||
"ψ": ["ps", "psi"],
|
|
||||||
"ω": ["om", "omega"],
|
|
||||||
"Γ": ["gga", "ggamma"],
|
|
||||||
"Δ": ["gde", "gdelta"],
|
|
||||||
"Θ": ["gth", "gtheta"],
|
|
||||||
"Λ": ["gla", "glambda"],
|
|
||||||
"Ξ": ["gxi"],
|
|
||||||
"Π": ["gpi"],
|
|
||||||
"Σ": ["gsi", "gsigma"],
|
|
||||||
"Φ": ["gph", "gphi"],
|
|
||||||
"Ψ": ["gps", "gpsi"],
|
|
||||||
"Ω": ["gom", "gomega"],
|
|
||||||
}
|
|
||||||
for(const [char, shorts] of Object.entries(shorthands)) {
|
|
||||||
expect(parseName(char)).to.equal(char)
|
|
||||||
for(const short of shorts)
|
|
||||||
expect(parseName(short)).to.equal(char)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it("parses array elements into sub", function() {
|
|
||||||
expect(parseName("u[n+1]")).to.equal("uₙ₊₁")
|
|
||||||
expect(parseName("u[(n+x)]")).to.equal("u₍ₙ₊ₓ₎")
|
|
||||||
expect(parseName("u[A]")).to.equal("uA")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("removes disallowed characters when indicated", function() {
|
|
||||||
const disallowed = "xnπℝℕ\\∪∩[] ()^/^/÷*×+=1234567890¹²³⁴⁵⁶⁷⁸⁹⁰-"
|
|
||||||
expect(parseName(disallowed)).to.equal("")
|
|
||||||
expect(parseName("AA" + disallowed)).to.equal("AA")
|
|
||||||
expect(parseName(disallowed, false)).to.equal(disallowed)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("is able to do all three at once", function() {
|
|
||||||
expect(parseName("al[n+1]+n")).to.equal("αₙ₊₁")
|
|
||||||
expect(parseName("al[n+1]+n", false)).to.equal("αₙ₊₁+n")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#getRandomColor", function() {
|
|
||||||
it("provides a valid color", function() {
|
|
||||||
const colorReg = /^#[A-F\d]{6}$/
|
|
||||||
for(let i = 0; i < 50; i++)
|
|
||||||
expect(getRandomColor()).to.match(colorReg)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#escapeHTML", function() {
|
|
||||||
it("escapes ampersands", function() {
|
|
||||||
expect(escapeHTML("&")).to.equal("&")
|
|
||||||
expect(escapeHTML("High & Mighty")).to.equal("High & Mighty")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("escapes injected HTML tags", function() {
|
|
||||||
expect(escapeHTML("<script>alert('Injected!')</script>")).to.equal("<script>alert('Injected!')</script>")
|
|
||||||
expect(escapeHTML('<a href="javascript:alert()">Link</a>')).to.equal('<a href="javascript:alert()">Link</a>')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#exponentsToExpression", function() {
|
|
||||||
it("transforms exponents to power expression", function() {
|
|
||||||
expect(exponentsToExpression("x¹²³⁴⁵⁶⁷⁸⁹⁰")).to.equal("x^1234567890")
|
|
||||||
expect(exponentsToExpression("x¹²+y³⁴")).to.equal("x^12+y^34")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -15,22 +15,16 @@
|
||||||
* You should have received a copy of the GNU General Public License
|
* 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/>.
|
||||||
*/
|
*/
|
||||||
import * as fs from "./mock/fs.mjs"
|
import * as fs from "./mock/fs.mjs";
|
||||||
import Qt from "./mock/qt.mjs"
|
import Qt from "./mock/qt.mjs";
|
||||||
import { MockHelper } from "./mock/helper.mjs"
|
import { MockHelper } from "./mock/helper.mjs";
|
||||||
import { MockLatex } from "./mock/latex.mjs"
|
import { MockLatex } from "./mock/latex.mjs";
|
||||||
|
import Modules from "../src/module/index.mjs";
|
||||||
import { use } from "chai"
|
|
||||||
import spies from "chai-spies"
|
|
||||||
import promised from "chai-as-promised"
|
|
||||||
|
|
||||||
function setup() {
|
function setup() {
|
||||||
use(promised)
|
|
||||||
const { spy } = use(spies)
|
|
||||||
|
|
||||||
globalThis.Helper = new MockHelper()
|
globalThis.Helper = new MockHelper()
|
||||||
globalThis.Latex = new MockLatex()
|
globalThis.Latex = new MockLatex()
|
||||||
globalThis.chaiPlugins = { spy }
|
Modules.Latex.initialize({ latex: Latex, helper: Helper })
|
||||||
}
|
}
|
||||||
|
|
||||||
setup()
|
setup()
|
||||||
|
|
|
@ -19,46 +19,46 @@
|
||||||
import { describe, it } from "mocha"
|
import { describe, it } from "mocha"
|
||||||
import { expect } from "chai"
|
import { expect } from "chai"
|
||||||
|
|
||||||
// import { Domain, parseDomainSimple } from "../../src/math/domain.mjs"
|
import { Domain, parseDomainSimple } from "../../src/math/domain.mjs"
|
||||||
//
|
|
||||||
// describe("math.domain", function() {
|
describe("math.domain", function() {
|
||||||
// describe("#parseDomainSimple", function() {
|
describe("#parseDomainSimple", function() {
|
||||||
// it("returns predefined domains", function() {
|
it("returns predefined domains", function() {
|
||||||
// const predefinedToCheck = [
|
const predefinedToCheck = [
|
||||||
// // Real domains
|
// Real domains
|
||||||
// { domain: Domain.R, shortcuts: ["R", "ℝ"] },
|
{ domain: Domain.R, shortcuts: ["R", "ℝ"] },
|
||||||
// // Zero exclusive real domains
|
// Zero exclusive real domains
|
||||||
// { domain: Domain.RE, shortcuts: ["RE", "R*", "ℝ*"] },
|
{ domain: Domain.RE, shortcuts: ["RE", "R*", "ℝ*"] },
|
||||||
// // Real positive domains
|
// Real positive domains
|
||||||
// { domain: Domain.RP, shortcuts: ["RP", "R+", "ℝ⁺", "ℝ+"] },
|
{ domain: Domain.RP, shortcuts: ["RP", "R+", "ℝ⁺", "ℝ+"] },
|
||||||
// // Zero-exclusive real positive domains
|
// Zero-exclusive real positive domains
|
||||||
// { domain: Domain.RPE, shortcuts: ["RPE", "REP", "R+*", "R*+", "ℝ*⁺", "ℝ⁺*", "ℝ*+", "ℝ+*"] },
|
{ domain: Domain.RPE, shortcuts: ["RPE", "REP", "R+*", "R*+", "ℝ*⁺", "ℝ⁺*", "ℝ*+", "ℝ+*"] },
|
||||||
// // Real negative domain
|
// Real negative domain
|
||||||
// { domain: Domain.RM, shortcuts: ["RM", "R-", "ℝ⁻", "ℝ-"] },
|
{ domain: Domain.RM, shortcuts: ["RM", "R-", "ℝ⁻", "ℝ-"] },
|
||||||
// // Zero-exclusive real negative domains
|
// Zero-exclusive real negative domains
|
||||||
// { domain: Domain.RME, shortcuts: ["RME", "REM", "R-*", "R*-", "ℝ⁻*", "ℝ*⁻", "ℝ-*", "ℝ*-"] },
|
{ domain: Domain.RME, shortcuts: ["RME", "REM", "R-*", "R*-", "ℝ⁻*", "ℝ*⁻", "ℝ-*", "ℝ*-"] },
|
||||||
// // Natural integers domain
|
// Natural integers domain
|
||||||
// { domain: Domain.N, shortcuts: ["ℕ", "N", "ZP", "Z+", "ℤ⁺", "ℤ+"] },
|
{ domain: Domain.N, shortcuts: ["ℕ", "N", "ZP", "Z+", "ℤ⁺", "ℤ+"] },
|
||||||
// // Zero-exclusive natural integers domain
|
// Zero-exclusive natural integers domain
|
||||||
// { domain: Domain.NE, shortcuts: ["NE", "NP", "N*", "N+", "ℕ*", "ℕ⁺", "ℕ+", "ZPE", "ZEP", "Z+*", "Z*+", "ℤ⁺*", "ℤ*⁺", "ℤ+*", "ℤ*+"] },
|
{ domain: Domain.NE, shortcuts: ["NE", "NP", "N*", "N+", "ℕ*", "ℕ⁺", "ℕ+", "ZPE", "ZEP", "Z+*", "Z*+", "ℤ⁺*", "ℤ*⁺", "ℤ+*", "ℤ*+"] },
|
||||||
// // Logarithmic natural domains
|
// Logarithmic natural domains
|
||||||
// { domain: Domain.NLog, shortcuts: ["NLOG", "ℕˡᵒᵍ", "ℕLOG"] },
|
{ domain: Domain.NLog, shortcuts: ["NLOG", "ℕˡᵒᵍ", "ℕLOG"] },
|
||||||
// // All integers domains
|
// All integers domains
|
||||||
// { domain: Domain.Z, shortcuts: ["Z", "ℤ"] },
|
{ domain: Domain.Z, shortcuts: ["Z", "ℤ"] },
|
||||||
// // Zero-exclusive all integers domain
|
// Zero-exclusive all integers domain
|
||||||
// { domain: Domain.ZE, shortcuts: ["ZE", "Z*", "ℤ*"] },
|
{ domain: Domain.ZE, shortcuts: ["ZE", "Z*", "ℤ*"] },
|
||||||
// // Negative integers domain
|
// Negative integers domain
|
||||||
// { domain: Domain.ZM, shortcuts: ["ZM", "Z-", "ℤ⁻", "ℤ-"] },
|
{ domain: Domain.ZM, shortcuts: ["ZM", "Z-", "ℤ⁻", "ℤ-"] },
|
||||||
// // Zero-exclusive negative integers domain
|
// Zero-exclusive negative integers domain
|
||||||
// { domain: Domain.ZME, shortcuts: ["ZME", "ZEM", "Z-*", "Z*-", "ℤ⁻*", "ℤ*⁻", "ℤ-*", "ℤ*-"] },
|
{ domain: Domain.ZME, shortcuts: ["ZME", "ZEM", "Z-*", "Z*-", "ℤ⁻*", "ℤ*⁻", "ℤ-*", "ℤ*-"] },
|
||||||
// ]
|
]
|
||||||
//
|
|
||||||
// // Real domains
|
// Real domains
|
||||||
// for(const { domain, shortcuts } of predefinedToCheck)
|
for(const { domain, shortcuts } of predefinedToCheck)
|
||||||
// for(const shortcut of shortcuts)
|
for(const shortcut of shortcuts)
|
||||||
// expect(parseDomainSimple(shortcut)).to.be.equal(domain)
|
expect(parseDomainSimple(shortcut)).to.be.equal(domain)
|
||||||
// })
|
})
|
||||||
//
|
|
||||||
// it("")
|
it("")
|
||||||
// })
|
})
|
||||||
// })
|
})
|
||||||
|
|
|
@ -1,181 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Load prior tests
|
|
||||||
import "../basics/utils.mjs"
|
|
||||||
import "../module/latex.mjs"
|
|
||||||
import "../module/expreval.mjs"
|
|
||||||
import "../module/objects.mjs"
|
|
||||||
|
|
||||||
import { describe, it } from "mocha"
|
|
||||||
import { expect } from "chai"
|
|
||||||
|
|
||||||
import { executeExpression, Expression } from "../../src/math/expression.mjs"
|
|
||||||
import ExprEval from "../../src/module/expreval.mjs"
|
|
||||||
|
|
||||||
|
|
||||||
describe("Math/Expression", function() {
|
|
||||||
describe("#constructor", function() {
|
|
||||||
it("accepts strings", function() {
|
|
||||||
expect(() => new Expression("2+3")).to.not.throw
|
|
||||||
expect(() => new Expression("x+2")).to.not.throw
|
|
||||||
})
|
|
||||||
|
|
||||||
it("accepts already parsed expressions", function() {
|
|
||||||
expect(() => new Expression(ExprEval.parse("2+3"))).to.not.throw
|
|
||||||
expect(() => new Expression(ExprEval.parse("x+2"))).to.not.throw
|
|
||||||
})
|
|
||||||
|
|
||||||
it("doesn't accept anything else", function() {
|
|
||||||
expect(() => new Expression()).to.throw("Cannot create an expression with undefined.")
|
|
||||||
expect(() => new Expression(12)).to.throw("Cannot create an expression with a Number.")
|
|
||||||
expect(() => new Expression({})).to.throw("Cannot create an expression with a Object.")
|
|
||||||
expect(() => new Expression(true)).to.throw("Cannot create an expression with a Boolean.")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#variables", function() {
|
|
||||||
it("returns a list of variables for non-constant expressions", function() {
|
|
||||||
expect(new Expression("x+1").variables()).to.deep.equal(["x"])
|
|
||||||
expect(new Expression("x+n").variables()).to.deep.equal(["x", "n"])
|
|
||||||
expect(new Expression("u[n] + A.x").variables()).to.deep.equal(["u", "n", "A"])
|
|
||||||
})
|
|
||||||
|
|
||||||
it("returns an empty array if the expression is constant", function() {
|
|
||||||
expect(new Expression("2+1").variables()).to.deep.equal([])
|
|
||||||
expect(new Expression("sin π").variables()).to.deep.equal([])
|
|
||||||
expect(new Expression("e^3").variables()).to.deep.equal([])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#isConstant", function() {
|
|
||||||
it("returns true if neither x nor n are included into the expression", function() {
|
|
||||||
expect(new Expression("2+1").isConstant()).to.be.true
|
|
||||||
expect(new Expression("e^3").isConstant()).to.be.true
|
|
||||||
expect(new Expression("2+f(3)").isConstant()).to.be.true
|
|
||||||
expect(new Expression("sin A.x").isConstant()).to.be.true
|
|
||||||
})
|
|
||||||
|
|
||||||
it("returns false if either x or n are included into the expression", function() {
|
|
||||||
expect(new Expression("2+x").isConstant()).to.be.false
|
|
||||||
expect(new Expression("e^n").isConstant()).to.be.false
|
|
||||||
expect(new Expression("2+f(x)").isConstant()).to.be.false
|
|
||||||
expect(new Expression("n + sin x").isConstant()).to.be.false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#requiredObjects", function() {
|
|
||||||
it("returns the list of objects that need to be registered for this expression", function() {
|
|
||||||
expect(new Expression("x^n").requiredObjects()).to.deep.equal([])
|
|
||||||
expect(new Expression("2+f(3)").requiredObjects()).to.deep.equal(["f"])
|
|
||||||
expect(new Expression("A.x+x").requiredObjects()).to.deep.equal(["A"])
|
|
||||||
expect(new Expression("2+f(sin A.x)+n").requiredObjects()).to.deep.equal(["f", "A"])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe.skip("#allRequirementsFulfilled", function() {
|
|
||||||
// TODO: Make tests for objects
|
|
||||||
})
|
|
||||||
|
|
||||||
describe.skip("#undefinedVariables", function() {
|
|
||||||
// TODO: Make tests for objects
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#toEditableString", function() {
|
|
||||||
it("should return a readable expression", function() {
|
|
||||||
expect(new Expression("2+1").toEditableString()).to.equal("3")
|
|
||||||
expect(new Expression("2+x").toEditableString()).to.equal("(2 + x)")
|
|
||||||
expect(new Expression("x*2+x/3").toEditableString()).to.equal("((x * 2) + (x / 3))")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should be able to be reparsed and equal the same expression", function() {
|
|
||||||
const exprs = ["5", "x/2", "4/2", "sin x"]
|
|
||||||
for(const expr of exprs) {
|
|
||||||
const exprObj = new Expression(expr)
|
|
||||||
expect(new Expression(exprObj.toEditableString()).calc).to.deep.equal(exprObj.calc)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#execute", function() {
|
|
||||||
it("returns the result of the computation of the expression", function() {
|
|
||||||
expect(new Expression("2+3").execute()).to.equal(5)
|
|
||||||
expect(new Expression("2+3").execute(10)).to.equal(5)
|
|
||||||
expect(new Expression("2+x").execute(10)).to.equal(12)
|
|
||||||
expect(new Expression("sin x").execute(Math.PI)).to.be.approximately(0, Number.EPSILON)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("returns the cached value if the expression can be cached", function() {
|
|
||||||
const exprs = ["2+3", "x/2", "4/2", "sin x"]
|
|
||||||
for(const expr of exprs) {
|
|
||||||
const exprObj = new Expression(expr)
|
|
||||||
if(exprObj.canBeCached)
|
|
||||||
expect(exprObj.execute()).to.equal(exprObj.cachedValue)
|
|
||||||
else
|
|
||||||
expect(exprObj.execute()).to.not.equal(exprObj.cachedValue)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
it("throws an error if some variables are undefined.", function() {
|
|
||||||
expect(() => new Expression("x+n").execute()).to.throw("Undefined variable n.")
|
|
||||||
expect(() => new Expression("sin A.t").execute()).to.throw("Undefined variable A.")
|
|
||||||
expect(() => new Expression("f(3)").execute()).to.throw("Undefined variable f.")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#simplify", function() {
|
|
||||||
it("returns an expression with just the result when no constant or object are used", function() {
|
|
||||||
expect(new Expression("2+2").simplify(Math.PI/2)).to.deep.equal(new Expression("4"))
|
|
||||||
expect(new Expression("x+3").simplify(5)).to.deep.equal(new Expression("8"))
|
|
||||||
expect(new Expression("sin x").simplify(Math.PI/2)).to.deep.equal(new Expression("1"))
|
|
||||||
expect(new Expression("0*e^x").simplify(Math.PI/2)).to.deep.equal(new Expression("0"))
|
|
||||||
})
|
|
||||||
|
|
||||||
it("returns a simplified version of the expression if constants are used", function() {
|
|
||||||
const original = new Expression("e^x").simplify(2)
|
|
||||||
const to = new Expression("e^2")
|
|
||||||
expect(original.toEditableString()).to.deep.equal(to.toEditableString())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#toString", function() {
|
|
||||||
it("returns a human readable string of the expression", function() {
|
|
||||||
expect(new Expression("-2-3").toString()).to.equal("-5")
|
|
||||||
expect(new Expression("0.2+0.1").toString()).to.equal("0.3")
|
|
||||||
expect(new Expression("sin x").toString()).to.equal("sin x")
|
|
||||||
expect(new Expression("sin π").toString()).to.equal("sin π")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should add a sign if the option is passed", function() {
|
|
||||||
expect(new Expression("-2-3").toString(true)).to.equal("-5")
|
|
||||||
expect(new Expression("2+3").toString(true)).to.equal("+5")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("#executeExpression", function() {
|
|
||||||
it("directly computes the result of the expression with no variable", function() {
|
|
||||||
expect(executeExpression("2+3")).to.equal(5)
|
|
||||||
expect(executeExpression("sin (π/2)")).to.equal(1)
|
|
||||||
expect(executeExpression("e^3")).to.be.approximately(Math.pow(Math.E, 3), Number.EPSILON)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("throws an error if variables are employed", function() {
|
|
||||||
expect(() => executeExpression("x+n")).to.throw("Undefined variable n.")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,59 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
export class MockCanvas {
|
|
||||||
constructor(mockLoading = false) {
|
|
||||||
this.mockLoading = mockLoading
|
|
||||||
}
|
|
||||||
|
|
||||||
getContext(context) {
|
|
||||||
throw new Error("MockCanvas.getContext not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
markDirty(rect) {
|
|
||||||
this.requestPaint()
|
|
||||||
}
|
|
||||||
|
|
||||||
loadImageAsync(image) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Image loading is instantaneous.
|
|
||||||
* @param {string} image
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
isImageLoading(image) {
|
|
||||||
return this.mockLoading
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Image loading is instantaneous.
|
|
||||||
* @param {string} image
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
isImageLoaded(image) {
|
|
||||||
return !this.mockLoading
|
|
||||||
}
|
|
||||||
|
|
||||||
requestPaint() {
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export class MockDialog {
|
|
||||||
constructor() {}
|
|
||||||
|
|
||||||
show() {}
|
|
||||||
}
|
|