Compare commits
No commits in common. "master" and "rollup-js" have entirely different histories.
6
.gitignore
vendored
|
@ -37,10 +37,8 @@ docs/html
|
|||
*.lpf
|
||||
*.lgg
|
||||
|
||||
# Tests
|
||||
common/coverage/
|
||||
**/.coverage
|
||||
|
||||
# npm
|
||||
common/node_modules
|
||||
common/coverage/
|
||||
common/.coverage
|
||||
runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/index.mjs*
|
||||
|
|
20
README.md
|
@ -1,4 +1,4 @@
|
|||
#  LogarithmPlotter
|
||||
#  LogarithmPlotter
|
||||
|
||||
[](https://ci.ad5001.eu/Ad5001/LogarithmPlotter)
|
||||
[](https://hosted.weblate.org/engage/logarithmplotter/)
|
||||
|
@ -24,7 +24,7 @@ First, you'll need to install all the required dependencies:
|
|||
- [npm](https://npmjs.com) (or [yarn](https://yarnpkg.com/)), go to the `common` directory, and run `npm install` (or `yarn install`).
|
||||
|
||||
You can simply run LogarithmPlotter using `python3 run.py`. It automatically compiles the language files (requires
|
||||
`pyside6-lrelease` to be installed and in path), and the JavaScript modules.
|
||||
`lrelease` to be installed and in path), and the JavaScript modules.
|
||||
|
||||
If you do not wish do recompile the files again on every run, you can use the build script (`scripts/build.sh`) and run
|
||||
`python3 build/runtime-pyside6/LogarithmPlotter/logarithmplotter.py`.
|
||||
|
@ -68,19 +68,13 @@ To run LogarithmPlotter's tests, follow these steps:
|
|||
|
||||
- Python
|
||||
- Install python3 and [poetry](https://python-poetry.org/)
|
||||
- Create and activate virtual env (recommended)
|
||||
- Go into `runtime-pyside6` and run `poetry install --with test`
|
||||
- ECMAScript
|
||||
- Install node with npm
|
||||
- Go into `common` and run `npm install -D`
|
||||
|
||||
Finally, to actually run the tests:
|
||||
- Run `poetry install --with test`
|
||||
- Run `scripts/run-tests.sh`
|
||||
|
||||
## Legal notice
|
||||
|
||||
LogarithmPlotter - 2D plotter software to make Bode plots, sequences and repartition functions.
|
||||
Copyright (C) 2021-2025 Ad5001 <mail@ad5001.eu>
|
||||
Copyright (C) 2021-2024 Ad5001 <mail@ad5001.eu>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -95,8 +89,8 @@ Finally, to actually run the tests:
|
|||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
See LICENSE.md for more details. Language files translations located at assets/i18n are licensed under GNU GPL3.0+ and
|
||||
are copyrighted by their original authors:
|
||||
Language files translations located at LogarithmPlotter/i18n are licensed under GNU GPL3.0+ and are copyrighted by their
|
||||
original authors. See LICENSE.md for more details:
|
||||
|
||||
- 🇭🇺 Hungarian translation by [Óvári](https://github.com/ovari)
|
||||
- 🇳🇴 Norwegian translation by [Allan Nordhøy](https://github.com/comradekingu)
|
||||
|
@ -109,5 +103,5 @@ of [ndef.parser](https://web.archive.org/web/20111023001618/http://www.undefined
|
|||
<r@undefined.ch>, ported to javascript by Matthew Crumley
|
||||
<email@matthewcrumley.com> (http://silentmatt.com/), and then to QMLJS by Ad5001.
|
||||
|
||||
All files in (common/src/lib/expr-eval/) except integration.mjs are licensed
|
||||
All files in (LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/lib/expr-eval/) except integration.mjs are licensed
|
||||
under the [MIT License](https://raw.githubusercontent.com/silentmatt/expr-eval/master/LICENSE.txt).
|
||||
|
|
1597
assets/i18n/lp_ta.ts
|
@ -1,2 +1,2 @@
|
|||
#!/bin/bash
|
||||
pyside6-lrelease *.ts
|
||||
lrelease *.ts
|
||||
|
|
|
@ -21,7 +21,7 @@ replace() {
|
|||
|
||||
rm ../qml/eu/ad5001/LogarithmPlotter/js/index.mjs # Remove index which should not be scanned
|
||||
|
||||
files=$(find ../../common/src -name '*.mjs')
|
||||
files=$(find .. -name *.mjs)
|
||||
for file in $files; do
|
||||
echo "Moving '$file' to '${file%.*}.js'..."
|
||||
mv "$file" "${file%.*}.js"
|
||||
|
@ -33,14 +33,12 @@ for file in $files; do
|
|||
replace "${file%.*}.js" "^export" "/*export*/"
|
||||
replace "${file%.*}.js" "async " "/*async */"
|
||||
replace "${file%.*}.js" "await" "/*await */"
|
||||
replace "${file%.*}.js" " #" "// #"
|
||||
replace "${file%.*}.js" "this.#" "/*this.#*/"
|
||||
done
|
||||
|
||||
echo "----------------------------"
|
||||
echo "| Updating translations... |"
|
||||
echo "----------------------------"
|
||||
pyside6-lupdate -extensions js,qs,qml,py -recursive ../../common/src -recursive ../../runtime-pyside6/LogarithmPlotter -ts lp_*.ts
|
||||
lupdate -extensions js,qs,qml,py -recursive .. -ts lp_*.ts
|
||||
# Updating locations in files
|
||||
for lp in *.ts; do
|
||||
echo "Replacing locations in $lp..."
|
||||
|
@ -57,9 +55,7 @@ for file in $files; do
|
|||
replace "$file" "/*async */" "async "
|
||||
replace "$file" "^/*export*/" "export"
|
||||
replace "$file" "^/*export default*/" "export default"
|
||||
replace "$file" '.mjs"*/' '.mjs"'
|
||||
replace "$file" "^/*import" "import"
|
||||
replace "$file" "^/*export" "export"
|
||||
replace "$file" "// #" " #"
|
||||
replace "$file" "/*this.#*/" "this.#"
|
||||
replace "$file" '.mjs"*/$' '.mjs"'
|
||||
done
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
../common/appearance.svg
|
|
@ -1 +0,0 @@
|
|||
../common/appearance.svg
|
|
@ -1 +0,0 @@
|
|||
../common/arrow.svg
|
|
@ -1 +0,0 @@
|
|||
../common/position.svg
|
|
@ -1 +0,0 @@
|
|||
../common/angle.svg
|
|
@ -1 +0,0 @@
|
|||
../common/angle.svg
|
|
@ -1 +0,0 @@
|
|||
../common/appearance.svg
|
|
@ -1 +0,0 @@
|
|||
../common/target.svg
|
|
@ -1 +0,0 @@
|
|||
../common/position.svg
|
|
@ -1 +0,0 @@
|
|||
../common/label.svg
|
|
@ -1 +0,0 @@
|
|||
../common/angle.svg
|
|
@ -1 +0,0 @@
|
|||
../common/position.svg
|
|
@ -1 +0,0 @@
|
|||
../common/position.svg
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
1
assets/icons/settings/custom/Display Mode.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../common/appearance.svg
|
1
assets/icons/settings/custom/Display Style.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../common/appearance.svg
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
1
assets/icons/settings/custom/Label Position.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../common/arrow.svg
|
1
assets/icons/settings/custom/Label X.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../common/position.svg
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
1
assets/icons/settings/custom/Phase.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../common/angle.svg
|
1
assets/icons/settings/custom/Point Style.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../common/appearance.svg
|
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 2 KiB |
1
assets/icons/settings/custom/Target Element.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../common/target.svg
|
1
assets/icons/settings/custom/Target Value Position.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../common/position.svg
|
1
assets/icons/settings/custom/Text.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../common/label.svg
|
1
assets/icons/settings/custom/Unit.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../common/angle.svg
|
1
assets/icons/settings/custom/X.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../common/position.svg
|
1
assets/icons/settings/custom/Y.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../common/position.svg
|
1
assets/icons/settings/custom/ω_0.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../common/angle.svg
|
|
@ -1,171 +1,64 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="48.0px"
|
||||
height="48.0px"
|
||||
viewBox="0 0 48.0 48.0"
|
||||
width="24.0px"
|
||||
height="24.0px"
|
||||
viewBox="0 0 24.0 24.0"
|
||||
version="1.1"
|
||||
id="SVGRoot"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xml:space="preserve"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<title
|
||||
id="title38896">LogarithmPlotter Icon</title>
|
||||
<defs
|
||||
id="defs2254">
|
||||
<linearGradient
|
||||
id="linearGradient27593">
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:0.15000001;"
|
||||
offset="0"
|
||||
id="stop27589" />
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop27591" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient13467">
|
||||
<stop
|
||||
style="stop-color:#808080;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop13463" />
|
||||
<stop
|
||||
style="stop-color:#666666;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop13465" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient8377">
|
||||
<stop
|
||||
style="stop-color:#ebebeb;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop8373" />
|
||||
<stop
|
||||
style="stop-color:#bfbfbf;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop8375" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
xlink:href="#linearGradient8377"
|
||||
id="linearGradient8379"
|
||||
x1="12"
|
||||
y1="4.8570137"
|
||||
x2="12"
|
||||
y2="21.105883"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
xlink:href="#linearGradient13467"
|
||||
id="linearGradient13469"
|
||||
x1="12"
|
||||
y1="9.5647058"
|
||||
x2="12"
|
||||
y2="21"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
xlink:href="#linearGradient27593"
|
||||
id="linearGradient27595"
|
||||
x1="28"
|
||||
y1="28"
|
||||
x2="42"
|
||||
y2="42"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
</defs>
|
||||
<g
|
||||
id="layer1">
|
||||
<rect
|
||||
style="fill:url(#linearGradient13469);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><title
|
||||
id="title836">LogarithmPlotter Icon v1.0</title><defs
|
||||
id="defs833" /><metadata
|
||||
id="metadata836"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title>LogarithmPlotter Icon v1.0</dc:title><cc:license
|
||||
rdf:resource="http://creativecommons.org/licenses/by-nc-sa/4.0/" /><dc:date>2021</dc:date><dc:creator><cc:Agent><dc:title>Ad5001</dc:title></cc:Agent></dc:creator><dc:rights><cc:Agent><dc:title>(c) Ad5001 2021 - All rights reserved</dc:title></cc:Agent></dc:rights></cc:Work><cc:License
|
||||
rdf:about="http://creativecommons.org/licenses/by-nc-sa/4.0/"><cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Reproduction" /><cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Distribution" /><cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Notice" /><cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Attribution" /><cc:prohibits
|
||||
rdf:resource="http://creativecommons.org/ns#CommercialUse" /><cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /><cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#ShareAlike" /></cc:License></rdf:RDF></metadata><g
|
||||
id="layer2"
|
||||
transform="matrix(1,0,0,0.94444444,0,1.1666667)"
|
||||
style="fill:#666666"><rect
|
||||
style="fill:#666666;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1546"
|
||||
width="18"
|
||||
height="18.105883"
|
||||
x="3"
|
||||
y="2.8941176"
|
||||
ry="2.3823531"
|
||||
rx="2.2499998"
|
||||
transform="matrix(2.2222222,0,0,2.0987654,-2.6666667,-0.07407404)" />
|
||||
<rect
|
||||
style="fill:url(#linearGradient8379);display:inline;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1546-7"
|
||||
width="18"
|
||||
height="18.105883"
|
||||
height="18"
|
||||
x="3"
|
||||
y="3"
|
||||
ry="2.3212669"
|
||||
rx="2.2499998"
|
||||
transform="matrix(2.2222222,0,0,2.1539961,-2.6666667,-2.4619883)" />
|
||||
</g>
|
||||
<g
|
||||
id="layer3"
|
||||
style="fill:#0000ff">
|
||||
<path
|
||||
id="path27475"
|
||||
style="fill:url(#linearGradient27595);fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 20,8 V 36 H 10 l 7,7 h 15 7 c 2.769997,0 5,-2.230003 5,-5 v -5 -1 z" />
|
||||
</g>
|
||||
<g
|
||||
id="layer1-6"
|
||||
style="stroke-width:2;stroke-dasharray:none"
|
||||
transform="matrix(2,0,0,2,0,1)">
|
||||
<rect
|
||||
ry="2.25" /></g><g
|
||||
id="layer2-6"
|
||||
transform="matrix(1,0,0,0.94444444,0,0.16666668)"
|
||||
style="fill:#f9f9f9"><rect
|
||||
style="fill:#f9f9f9;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1546-7"
|
||||
width="18"
|
||||
height="18"
|
||||
x="3"
|
||||
y="3"
|
||||
ry="2.25" /></g><g
|
||||
id="layer1"
|
||||
style="stroke-width:2;stroke-dasharray:none"><rect
|
||||
style="fill:#000000;fill-rule:evenodd;stroke-width:1.86898;stroke-dasharray:none;stroke-opacity:0"
|
||||
id="rect1410"
|
||||
width="14"
|
||||
height="2"
|
||||
x="5"
|
||||
y="15.5" />
|
||||
<rect
|
||||
style="fill:#000000;fill-rule:evenodd;stroke-width:2.06559;stroke-dasharray:none;stroke-opacity:0"
|
||||
y="15.5" /><rect
|
||||
style="fill:#000000;fill-rule:evenodd;stroke-width:2;stroke-dasharray:none;stroke-opacity:0"
|
||||
id="rect1412"
|
||||
width="2"
|
||||
height="16"
|
||||
x="8"
|
||||
y="3.5" />
|
||||
<path
|
||||
height="15"
|
||||
x="9"
|
||||
y="3.9768662" /><path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ff0000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path1529"
|
||||
d="m 18,3.5 c 0,7 -4,12 -13,12" />
|
||||
</g>
|
||||
<metadata
|
||||
id="metadata38894">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:title>LogarithmPlotter Icon</dc:title>
|
||||
<dc:date>2024-10-06</dc:date>
|
||||
<dc:creator>
|
||||
<cc:Agent>
|
||||
<dc:title>Adsooi <mail@ad5001.eu></dc:title>
|
||||
</cc:Agent>
|
||||
</dc:creator>
|
||||
<dc:rights>
|
||||
<cc:Agent>
|
||||
<dc:title>(c) Adsooi 2021-2024</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:rights>
|
||||
<cc:license
|
||||
rdf:resource="http://creativecommons.org/licenses/by-nc-sa/4.0/" />
|
||||
</cc:Work>
|
||||
<cc:License
|
||||
rdf:about="http://creativecommons.org/licenses/by-nc-sa/4.0/">
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Reproduction" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Distribution" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Notice" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Attribution" />
|
||||
<cc:prohibits
|
||||
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
|
||||
</cc:License>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
</svg>
|
||||
d="M 18,4 C 18,10.017307 13.40948,15.5 5,15.5" /></g></svg>
|
||||
|
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 3.1 KiB |
|
@ -3,7 +3,7 @@ Source: logarithmplotter
|
|||
Version: 0.6.0
|
||||
Architecture: all
|
||||
Maintainer: Ad5001 <mail@ad5001.eu>
|
||||
Depends: python3 (>= 3.9), python3-pip, python3-pyside6-essentials (>= 6.7.0), python3-pyside6-addons (>= 6.7), texlive-latex-base, dvipng
|
||||
Depends: python3 (>= 3.9), python3-pip, python3-pyside6-essentials (>= 6.7.0), texlive-latex-base, dvipng
|
||||
|
||||
Build-Depends: debhelper (>=11~), dh-python, dpkg-dev (>= 1.16.1~), python-setuptools
|
||||
Section: science
|
|
@ -1 +0,0 @@
|
|||
python3 (>= 3.9), python3-pip, python3-pyside6.qtcore (>= 6), python3-pyside6.qtgui (>= 6), python3-pyside6.qtqml (>= 6), python3-pyside6.qtwidgets (>= 6), python3-pyside6.qtquick (>= 6), python3-pyside6.qtquickcontrols2 (>= 6), qml6-module-qt-labs-platform (>= 6), qml6-module-qtquick-dialogs (>= 6), texlive-latex-base, dvipng
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright 2025 Ad5001 -->
|
||||
<!-- Copyright 2024 Ad5001 -->
|
||||
<component type="desktop-application">
|
||||
<id>eu.ad5001.LogarithmPlotter</id>
|
||||
<launchable type="desktop-id">logarithmplotter.desktop</launchable>
|
||||
|
@ -66,54 +66,50 @@
|
|||
<categories>
|
||||
<category>Science</category>
|
||||
<category>Education</category>
|
||||
<category>Qt</category>
|
||||
</categories>
|
||||
|
||||
<url type="homepage">https://apps.ad5001.eu/logarithmplotter/</url>
|
||||
<url type="bugtracker">https://git.ad5001.eu/Ad5001/LogarithmPlotter/issues/</url>
|
||||
<url type="help">https://git.ad5001.eu/Ad5001/LogarithmPlotter/wiki/</url>
|
||||
<url type="translate">https://hosted.weblate.org/engage/logarithmplotter/</url>
|
||||
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>https://apps.ad5001.eu/img/en/logarithmplotter/gain.png?v=0.6</image>
|
||||
<image xml:lang="de">https://apps.ad5001.eu/img/de/logarithmplotter/gain.png?v=0.6</image>
|
||||
<image xml:lang="fr">https://apps.ad5001.eu/img/fr/logarithmplotter/gain.png?v=0.6</image>
|
||||
<image xml:lang="hu">https://apps.ad5001.eu/img/hu/logarithmplotter/gain.png?v=0.6</image>
|
||||
<image xml:lang="no">https://apps.ad5001.eu/img/no/logarithmplotter/gain.png?v=0.6</image>
|
||||
<image xml:lang="es">https://apps.ad5001.eu/img/es/logarithmplotter/gain.png?v=0.6</image>
|
||||
<image>https://apps.ad5001.eu/img/en/logarithmplotter/gain.png?v=0.5</image>
|
||||
<image xml:lang="de">https://apps.ad5001.eu/img/de/logarithmplotter/gain.png?v=0.5</image>
|
||||
<image xml:lang="fr">https://apps.ad5001.eu/img/fr/logarithmplotter/gain.png?v=0.5</image>
|
||||
<image xml:lang="hu">https://apps.ad5001.eu/img/hu/logarithmplotter/gain.png?v=0.5</image>
|
||||
<image xml:lang="no">https://apps.ad5001.eu/img/no/logarithmplotter/gain.png?v=0.5</image>
|
||||
<caption>Main view of LogarithmPlotter showing an asymptotic Bode magnitude plot.</caption>
|
||||
<caption xml:lang="de">Die Hauptansicht des LogarithmPlotters zeigt eine asymptotische Bode-Magnitude-Darstellung.</caption>
|
||||
<caption xml:lang="fr">Vue principale de LogarithmPlotter montrant un tracé asymptotique d'une magnitude de Bode.</caption>
|
||||
<caption xml:lang="hu">A LogarithmPlotter fő nézete, amely egy aszimptotikus Bode-magnitúdó ábrát mutat.</caption>
|
||||
<caption xml:lang="no">Hovedvisning av LogarithmPlotter som viser et asymptotisk Bode-størrelsesplott.</caption>
|
||||
<caption xml:lang="es">Vista principal de LogarithmPlotter mostrando un gráfico asintótico de una magnitud de Bode.</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://apps.ad5001.eu/img/en/logarithmplotter/phase.png?v=0.6</image>
|
||||
<image xml:lang="de">https://apps.ad5001.eu/img/de/logarithmplotter/phase.png?v=0.6</image>
|
||||
<image xml:lang="fr">https://apps.ad5001.eu/img/fr/logarithmplotter/phase.png?v=0.6</image>
|
||||
<image xml:lang="hu">https://apps.ad5001.eu/img/hu/logarithmplotter/phase.png?v=0.6</image>
|
||||
<image xml:lang="no">https://apps.ad5001.eu/img/no/logarithmplotter/phase.png?v=0.6</image>
|
||||
<image xml:lang="es">https://apps.ad5001.eu/img/es/logarithmplotter/phase.png?v=0.6</image>
|
||||
<image>https://apps.ad5001.eu/img/en/logarithmplotter/phase.png?v=0.5</image>
|
||||
<image xml:lang="de">https://apps.ad5001.eu/img/de/logarithmplotter/phase.png?v=0.5</image>
|
||||
<image xml:lang="fr">https://apps.ad5001.eu/img/fr/logarithmplotter/phase.png?v=0.5</image>
|
||||
<image xml:lang="hu">https://apps.ad5001.eu/img/hu/logarithmplotter/phase.png?v=0.5</image>
|
||||
<image xml:lang="no">https://apps.ad5001.eu/img/no/logarithmplotter/phase.png?v=0.5</image>
|
||||
<caption>Main view of LogarithmPlotter showing an asymptotic Bode phase plot.</caption>
|
||||
<caption xml:lang="de">Hauptansicht des LogarithmPlotters mit einer asymptotischen Bode-Phasendarstellung.</caption>
|
||||
<caption xml:lang="fr">Vue principale de LogarithmPlotter montrant un tracé asymptotique d'une phase de Bode.</caption>
|
||||
<caption xml:lang="hu">A LogarithmPlotter fő nézete, amely egy aszimptotikus Bode-fázis ábrát mutat.</caption>
|
||||
<caption xml:lang="no">Hovedvisning av LogarithmPlotter som viser et asymptotisk Bode-fasediagram.</caption>
|
||||
<caption xml:lang="es">Vista principal de LogarithmPlotter mostrando un gráfico asintótico de una fase de Bode.</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://apps.ad5001.eu/img/en/logarithmplotter/welcome.png?v=0.6</image>
|
||||
<image xml:lang="de">https://apps.ad5001.eu/img/de/logarithmplotter/welcome.png?v=0.6</image>
|
||||
<image xml:lang="fr">https://apps.ad5001.eu/img/fr/logarithmplotter/welcome.png?v=0.6</image>
|
||||
<image xml:lang="hu">https://apps.ad5001.eu/img/hu/logarithmplotter/welcome.png?v=0.6</image>
|
||||
<image xml:lang="no">https://apps.ad5001.eu/img/no/logarithmplotter/welcome.png?v=0.6</image>
|
||||
<image xml:lang="es">https://apps.ad5001.eu/img/es/logarithmplotter/welcome.png?v=0.6</image>
|
||||
<image>https://apps.ad5001.eu/img/en/logarithmplotter/welcome.png?v=0.5</image>
|
||||
<image xml:lang="de">https://apps.ad5001.eu/img/de/logarithmplotter/welcome.png?v=0.5</image>
|
||||
<image xml:lang="fr">https://apps.ad5001.eu/img/fr/logarithmplotter/welcome.png?v=0.5</image>
|
||||
<image xml:lang="hu">https://apps.ad5001.eu/img/hu/logarithmplotter/welcome.png?v=0.5</image>
|
||||
<image xml:lang="no">https://apps.ad5001.eu/img/no/logarithmplotter/welcome.png?v=0.5</image>
|
||||
<caption>LogarithmPlotter's welcome page.</caption>
|
||||
<caption xml:lang="de">LogarithmPlotter's Willkommensseite.</caption>
|
||||
<caption xml:lang="fr">Page d'accueil de LogarithmPlotter.</caption>
|
||||
<caption xml:lang="hu">LogarithmPlotter üdvözlő oldala.</caption>
|
||||
<caption xml:lang="no">LogarithmPlotters velkomstside.</caption>
|
||||
<caption xml:lang="es">Página de bienvenida de LogarithmPlotter.</caption>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
|
||||
<mime-type xmlns="http://www.freedesktop.org/standards/shared-mime-info" type="application/x-logarithm-plot">
|
||||
<comment>Logarithmic Plot File</comment>
|
||||
<comment>Logarithm Plot File</comment>
|
||||
<comment xml:lang="fr">Fichier Graphe Logarithmique</comment>
|
||||
<icon name="application-x-logarithm-plot"/>
|
||||
<glob-deleteall/>
|
||||
|
|
|
@ -13,7 +13,7 @@ Unicode True
|
|||
!define WEBSITE "https://apps.ad5001.eu/logarithmplotter"
|
||||
!define VERSION_SHORT "0.6.0"
|
||||
!define APP_VERSION "${VERSION_SHORT}.0"
|
||||
!define COPYRIGHT "Ad5001 (c) 2021-2025"
|
||||
!define COPYRIGHT "Ad5001 (c) 2021-2024"
|
||||
!define DESCRIPTION "Create graphs with logarithmic scales."
|
||||
|
||||
!define REG_UNINSTALL "Software\Microsoft\Windows\CurrentVersion\Uninstall\LogarithmPlotter"
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
LPFv1{"xzoom":100,"yzoom":10,"xmin":0.2696454905834007,"ymax":33.115625,"xaxisstep":"4","yaxisstep":"π","xaxislabel":"","yaxislabel":"","logscalex":true,"linewidth":1,"showxgrad":true,"showygrad":true,"textsize":18,"history":[[["CreateNewObject",["A","Point",["A",true,"#941A97","name + value","1","0","above","●"]]],["EditedPosition",["A","Point","1","175.36","0","9.9"]],["CreateNewObject",["f","Function",["f",true,"#6E590E","name + value","x","ℝ⁺*","ℝ","application","above",1,true,true]]],["EditedProperty",["f","Function","expression","x","((x / 2) - 1)",true]],["CreateNewObject",["t","Text",["t",true,"#118455","null","1","0","center","New text",false]]],["EditedPosition",["t","Text","1","36.48","0","(-13.7)"]],["EditedProperty",["t","Text","text","New text","AEZA",false]],["CreateNewObject",["ω","Point",["ω",true,"#5A3A52","name","1","0","above","●"]]],["CreateNewObject",["G₀","Gain Bode",["G₀",true,"#5A3A52","name + value","ω","high","20","below",1,false]]],["EditedPosition",["ω","Point","1","17.76","0","(-8.9)"]],["EditedProperty",["G₀","Gain Bode","gain","20","10",true]],["EditedProperty",["G₀","Gain Bode","labelPosition","below","below-left",false]],["EditedProperty",["G₀","Gain Bode","pass","high","low",false]],["EditedProperty",["G₀","Gain Bode","labelX",1,62.61,false]],["CreateNewObject",["X","X Cursor",["X",true,"#5909A9","name + value","1",null,"left",true,3,"— — — — — — —","Next to target"]]],["EditedProperty",["X","X Cursor","x","1","5.04",true]],["CreateNewObject",["u","Sequence",["u",true,"#78929E","name + value",true,true,{"1":"n"},{"0":0},"above",1]]],["EditedProperty",["u","Sequence","defaultExpression",{"1":"n"},{"1":"n+1"},false]],["EditedProperty",["u","Sequence","defaultExpression",{"1":"n+1"},{"1":"n+1"},false]],["EditedProperty",["u","Sequence","baseValues",{"0":0},{"0":"-1"},false]],["EditedProperty",["u","Sequence","baseValues",{"0":"-1"},{"0":"-1"},false]],["CreateNewObject",["F_X","Repartition",["F_X",true,"#231931","name + value",{"0":"0"},"above",1]]],["EditedProperty",["F_X","Repartition","labelX",1,12.64,false]],["EditedProperty",["f","Function","labelPosition","above","right",false]],["EditedProperty",["f","Function","labelX",1,30,false]],["EditedProperty",["u","Sequence","labelX",1,3,false]],["EditedProperty",["F_X","Repartition","labelX",12.64,40,false]],["EditedProperty",["ω","Point","labelPosition","above","below",false]],["CreateNewObject",["ω₀","Point",["ω₀","#7C2981","name","name + value","1","0","above","●"]]],["CreateNewObject",["φ₀","Phase Bode",["φ₀",true,"#7C2981","name + value","ω₀","90","°","below",1]]],["EditedPosition",["ω₀","Point","1","3","0","(-8)"]],["EditedPosition",["ω₀","Point","3","2","(-8)","8"]],["EditedProperty",["ω₀","Point","labelPosition","above","above-right",false]],["EditedProperty",["u","Sequence","labelPosition","above","above-left",false]],["EditedProperty",["u","Sequence","labelX",3,20,false]],["EditedProperty",["G","Somme gains Bode","labelX",1,2,false]]],[]],"width":1000,"height":500,"objects":{"Point":[["A",true,"#941A97","name + value","175.36","9.9","above","●"],["ω",true,"#5A3A52","name","17.76","(-8.9)","below","●"],["ω₀",false,"name","name","2","8","above-right","●"]],"Function":[["f",true,"#6E590E","name + value","((x / 2) - 1)","ℝ⁺*","ℝ","application","right",30,true,true]],"Text":[["t",true,"#118455","null","36.48","(-13.7)","center","AEZA",false]],"Gain Bode":[["G₀",true,"#5A3A52","name + value","ω","low","10","below-left",62.61,false]],"Somme gains Bode":[["G",true,"#A83C72","name + value","above",2]],"X Cursor":[["X",true,"#5909A9","name + value","5.04",null,"left",true,null,"— — — — — — —","Next to target"]],"Sequence":[["u",true,"#78929E","name + value",true,true,{"1":"n+1"},{"0":"-1"},"above-left",20]],"Repartition":[["F_X",true,"#231931","name + value",{"0":"0"},"above",40]],"Phase Bode":[["φ₀",true,"#7C2981","name + value","ω₀","90","°","below",1]],"Somme phases Bode":[["φ",true,"#A08B14","name + value","above",1]]},"type":"logplotv1"}
|
17
ci/drone.yml
|
@ -12,29 +12,32 @@ steps:
|
|||
- git submodule update --init --recursive
|
||||
|
||||
- name: Build
|
||||
image: ad5001/ubuntu-pyside-xvfb:linux-6-latest-latex-node
|
||||
image: node:18-bookworm
|
||||
commands:
|
||||
- cd common && npm install && cd ..
|
||||
- apt update
|
||||
- apt install -y qtchooser qttools5-dev-tools
|
||||
# Start building
|
||||
- bash scripts/build.sh
|
||||
when:
|
||||
event: [ push, tag ]
|
||||
|
||||
- name: Unit Tests
|
||||
image: ad5001/ubuntu-pyside-xvfb:linux-6-latest-latex-node
|
||||
image: ad5001/ubuntu-pyside-xvfb:linux-6-latest-latex
|
||||
commands:
|
||||
- apt update
|
||||
- apt install -y npm
|
||||
- cd common && npm install -D && cd ..
|
||||
- xvfb-run bash scripts/run-tests.sh --no-rebuild
|
||||
when:
|
||||
event: [ push, tag ]
|
||||
|
||||
- name: File Tests
|
||||
image: ad5001/ubuntu-pyside-xvfb:linux-6-latest-latex-node
|
||||
image: ad5001/ubuntu-pyside-xvfb:linux-6-latest-latex
|
||||
commands:
|
||||
- xvfb-run python3 run.py --test-build --no-check-for-updates
|
||||
- xvfb-run python3 run.py --test-build --no-check-for-updates ./ci/test1.lpf
|
||||
- xvfb-run python3 run.py --test-build --no-check-for-updates ./ci/test2.lpf
|
||||
- xvfb-run python3 run.py --test-build --no-check-for-updates ./ci/all.lpf
|
||||
- xvfb-run python3 run.py --test-build --no-check-for-updates ./ci/magnitude.lpf
|
||||
- xvfb-run python3 run.py --test-build --no-check-for-updates ./ci/phase.lpf
|
||||
- xvfb-run python3 run.py --test-build --no-check-for-updates ./ci/stress.lpf
|
||||
when:
|
||||
event: [ push, tag ]
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
LPFv1{"xzoom":100,"yzoom":100,"xmin":0.5,"ymax":2,"xaxisstep":"4","yaxisstep":"pi/4","xaxislabel":"ω (rad/s)","yaxislabel":"φ (rad)","logscalex":true,"linewidth":2,"showxgrad":true,"showygrad":true,"textsize":20,"history":[[["CreateNewObject",["f","Function",["f",true,"#989E2D","name + value","x","ℝ⁺*","ℝ","application","above",1,true,true]]],["EditedProperty",["f","Function","expression","x","(x ^ 20)",true]],["EditedProperty",["f","Function","expression","(x ^ 20)","(20 * (log10 x))",true]],["DeleteObject",["f","Function",["f",true,"#989E2D","name + value","(20 * (log10 x))","ℝ⁺*","ℝ","application","above",1,true,true]]],["CreateNewObject",["ω","Point",["ω",true,"#995178","name","1","0","bottom","●"]]],["CreateNewObject",["φ₀","Phase Bode",["φ₀",true,"#995178","name + value","ω","90","°","below",1]]],["EditedProperty",["φ₀","Phase Bode","phase","90","0",true]],["EditedProperty",["φ₀","Phase Bode","unit","°","rad",false]],["EditedProperty",["ω","Point","y","0","((-pi) / 2)",true]],["EditedVisibility",["ω","Point","visible"]],["EditedProperty",["φ₀","Phase Bode","labelX",1,10,false]],["CreateNewObject",["ω₀","Point",["ω₀",true,"#037753","name","1","0","bottom","●"]]],["CreateNewObject",["φ₁","Phase Bode",["φ₁",true,"#037753","name + value","ω₀","90","°","below",1]]],["EditedProperty",["ω₀","Point","x","1","10",true]],["EditedProperty",["φ₁","Phase Bode","unit","°","rad",false]],["EditedProperty",["φ₁","Phase Bode","phase","90","(pi / 2)",true]],["EditedProperty",["ω₀","Point","x","10","5",true]],["EditedProperty",["ω₀","Point","labelPosition","bottom","top-left",false]],["EditedProperty",["φ₁","Phase Bode","labelX",1,2,false]],["EditedProperty",["φ","Somme phases Bode","labelX",1,2,false]],["ColorChanged",["φ","Somme phases Bode","#665B74","#550000"]],["EditedProperty",["ω₀","Point","labelPosition","top-left","above-left",false]],["EditedProperty",["ω₀","Point","labelPosition","above-left","above-right",false]]],[]],"width":1000,"height":500,"objects":{"Function":[],"Point":[["ω",false,"#995178","name","1","((-pi) / 2)","below","●"],["ω₀",true,"#037753","name","5","0","above-right","●"]],"Phase Bode":[["φ₀",true,"#995178","name","ω","0","rad","below",10],["φ₁",true,"#037753","name","ω₀","(pi / 2)","rad","below",2]],"Somme phases Bode":[["φ",true,"#550000","name + value","above",2]]},"type":"logplotv1"}
|
1539
common/package-lock.json
generated
|
@ -2,7 +2,7 @@
|
|||
"name": "logarithmplotter",
|
||||
"version": "0.6.0",
|
||||
"description": "2D plotter software to make Bode plots, sequences and distribution functions.",
|
||||
"main": "src/index.mjs",
|
||||
"main": "LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/autoload.mjs",
|
||||
"scripts": {
|
||||
"build": "rollup --config rollup.config.mjs",
|
||||
"test": "c8 mocha test/**/*.mjs"
|
||||
|
@ -24,12 +24,9 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^5.0.0",
|
||||
"@types/chai-spies": "^1.0.6",
|
||||
"@types/chai-as-promised": "^8.0.1",
|
||||
"@types/mocha": "^10.0.8",
|
||||
"chai": "^5.1.1",
|
||||
"chai-as-promised": "^8.0.0",
|
||||
"chai-spies": "^1.1.0",
|
||||
"esm": "^3.2.25",
|
||||
"mocha": "^10.7.3"
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -22,7 +22,7 @@ import { babel } from "@rollup/plugin-babel"
|
|||
import cleanup from "rollup-plugin-cleanup"
|
||||
|
||||
const src = "./src/index.mjs"
|
||||
const dest = "../build/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Common/index.mjs"
|
||||
const dest = "../build/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/index.mjs"
|
||||
|
||||
export default {
|
||||
input: src,
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* We do not inherit the DOM's Event, because not only the DOM part is unnecessary,
|
||||
* but also because it does not exist within Qt environments.
|
||||
*/
|
||||
|
||||
|
||||
export class BaseEvent {
|
||||
___name = ""
|
||||
|
||||
/**
|
||||
* @property {string} name - Name of the event.
|
||||
*/
|
||||
constructor(name) {
|
||||
this.___name = name
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.___name
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Base class for all classes which can emit events.
|
||||
*/
|
||||
export class BaseEventEmitter {
|
||||
static emits = []
|
||||
|
||||
/** @type {Record<string, Set<function>>} */
|
||||
#listeners = {}
|
||||
|
||||
constructor() {
|
||||
for(const eventType of this.constructor.emits) {
|
||||
this.#listeners[eventType] = new Set()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to an event that can be emitted by this object.
|
||||
*
|
||||
* @param {string} eventType - Name of the event to listen to. Throws an error if this object does not emit this kind of event.
|
||||
* @param {function(BaseEvent)} eventListener - The function to be called back when the event is emitted.
|
||||
*/
|
||||
on(eventType, eventListener) {
|
||||
if(eventType.includes(" ")) // Listen to several different events with the same listener.
|
||||
for(const type of eventType.split(" "))
|
||||
this.on(type, eventListener)
|
||||
else {
|
||||
if(!this.constructor.emits.includes(eventType)) {
|
||||
const className = this.constructor.name
|
||||
const eventTypes = this.constructor.emits.join(", ")
|
||||
throw new Error(`Cannot listen to unknown event ${eventType} in class ${className}. ${className} only emits: ${eventTypes}`)
|
||||
}
|
||||
if(!this.#listeners[eventType].has(eventListener))
|
||||
this.#listeners[eventType].add(eventListener)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a listener from an event that can be emitted by this object.
|
||||
*
|
||||
* @param {string} eventType - Name of the event that was listened to. Throws an error if this object does not emit this kind of event.
|
||||
* @param {function(BaseEvent)} eventListener - The function previously registered as a listener.
|
||||
* @returns {boolean} True if the listener was removed, false if it was not found.
|
||||
*/
|
||||
off(eventType, eventListener) {
|
||||
if(eventType.includes(" ")) { // Unlisten to several different events with the same listener.
|
||||
let found = false
|
||||
for(const type of eventType.split(" "))
|
||||
found ||= this.off(type, eventListener)
|
||||
return found
|
||||
} else {
|
||||
if(!this.constructor.emits.includes(eventType)) {
|
||||
const className = this.constructor.name
|
||||
const eventTypes = this.constructor.emits.join(", ")
|
||||
throw new Error(`Cannot listen to unknown event ${eventType} in class ${className}. ${className} only emits: ${eventTypes}`)
|
||||
}
|
||||
return this.#listeners[eventType].delete(eventListener)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event to all of its listeners.
|
||||
*
|
||||
* @param {BaseEvent} e
|
||||
*/
|
||||
emit(e) {
|
||||
if(!(e instanceof BaseEvent))
|
||||
throw new Error("Cannot emit non event object.")
|
||||
if(!this.constructor.emits.includes(e.name)) {
|
||||
const className = this.constructor.name
|
||||
const eventTypes = this.constructor.emits.join(", ")
|
||||
throw new Error(`Cannot emit event '${e.name}' from class ${className}. ${className} can only emit: ${eventTypes}`)
|
||||
}
|
||||
for(const listener of this.#listeners[e.name])
|
||||
listener(e)
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -95,15 +95,11 @@ export class Action {
|
|||
if(!Latex.enabled)
|
||||
throw new Error("Cannot render an item as LaTeX when LaTeX is disabled.")
|
||||
const imgDepth = History.imageDepth
|
||||
const renderArguments = [
|
||||
const { source, width, height } = await Latex.requestAsyncRender(
|
||||
latexString,
|
||||
imgDepth * (History.fontSize + 2),
|
||||
History.themeTextColor
|
||||
]
|
||||
let render = Latex.findPrerendered(...renderArguments)
|
||||
if(render === null)
|
||||
render = await Latex.requestAsyncRender(...renderArguments)
|
||||
const { source, width, height } = render
|
||||
)
|
||||
return `<img src="${source}" width="${width / imgDepth}" height="${height / imgDepth}" style="vertical-align: middle"/>`
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -19,7 +19,7 @@
|
|||
import Objects from "../module/objects.mjs"
|
||||
import Latex from "../module/latex.mjs"
|
||||
import * as MathLib from "../math/index.mjs"
|
||||
import { escapeHTML } from "../utils/index.mjs"
|
||||
import { escapeHTML } from "../utils.mjs"
|
||||
import { Action } from "./common.mjs"
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -18,11 +18,10 @@
|
|||
|
||||
import js from "./lib/polyfills/js.mjs"
|
||||
|
||||
export * as Utils from "./utils/index.mjs"
|
||||
|
||||
import * as Modules from "./module/index.mjs"
|
||||
import * as ObjsAutoload from "./objs/autoload.mjs"
|
||||
|
||||
export * as Modules from "./module/index.mjs"
|
||||
export * as MathLib from "./math/index.mjs"
|
||||
export * as HistoryLib from "./history/index.mjs"
|
||||
export * as Parsing from "./parsing/index.mjs"
|
||||
export * as Utils from "./utils.mjs"
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*
|
||||
* Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu)
|
||||
*
|
||||
* Copyright (c) 2015 Matthew Crumley, 2021-2025 Ad5001
|
||||
* Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
|
@ -111,7 +111,7 @@ function simplify(tokens, unaryOps, binaryOps, ternaryOps, values) {
|
|||
* In the given instructions, replaces variable by expr.
|
||||
* @param {Instruction[]} tokens
|
||||
* @param {string} variable
|
||||
* @param {ExprEvalExpression} expr
|
||||
* @param {number} expr
|
||||
* @return {Instruction[]}
|
||||
*/
|
||||
function substitute(tokens, variable, expr) {
|
||||
|
@ -171,6 +171,9 @@ function evaluate(tokens, expr, values) {
|
|||
nstack.push(n1 ? !!evaluate(n2, expr, values) : false)
|
||||
} else if(item.value === "or") {
|
||||
nstack.push(n1 ? true : !!evaluate(n2, expr, values))
|
||||
} else if(item.value === "=") {
|
||||
f = expr.binaryOps[item.value]
|
||||
nstack.push(f(n1, evaluate(n2, expr, values), values))
|
||||
} else {
|
||||
f = expr.binaryOps[item.value]
|
||||
nstack.push(f(resolveExpression(n1, values), resolveExpression(n2, values)))
|
||||
|
@ -487,6 +490,18 @@ export class ExprEvalExpression {
|
|||
return evaluate(this.tokens, this, values)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of symbols (string of characters) in the expressions.
|
||||
* Can be functions, constants, or variables.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
symbols(options) {
|
||||
options = options || {}
|
||||
const vars = []
|
||||
getSymbols(this.tokens, vars, options)
|
||||
return vars
|
||||
}
|
||||
|
||||
toString() {
|
||||
return expressionToString(this.tokens, false)
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*
|
||||
* Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu)
|
||||
*
|
||||
* Copyright (c) 2015 Matthew Crumley, 2021-2025 Ad5001
|
||||
* Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*
|
||||
* Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu)
|
||||
*
|
||||
* Copyright (c) 2015 Matthew Crumley, 2021-2025 Ad5001
|
||||
* Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
|
@ -47,7 +47,9 @@ const optionNameMap = {
|
|||
"not": "logical",
|
||||
"?": "conditional",
|
||||
":": "conditional",
|
||||
//'=': 'assignment', // Disable assignment
|
||||
"[": "array"
|
||||
//'()=': 'fndef' // Diable function definition
|
||||
}
|
||||
|
||||
export class Parser {
|
||||
|
@ -107,6 +109,7 @@ export class Parser {
|
|||
and: Polyfill.andOperator,
|
||||
or: Polyfill.orOperator,
|
||||
"in": Polyfill.inOperator,
|
||||
"=": Polyfill.setVar,
|
||||
"[": Polyfill.arrayIndex
|
||||
}
|
||||
|
||||
|
@ -120,13 +123,18 @@ export class Parser {
|
|||
min: Polyfill.min,
|
||||
max: Polyfill.max,
|
||||
hypot: Math.hypot || Polyfill.hypot,
|
||||
pyt: Math.hypot || Polyfill.hypot,
|
||||
pyt: Math.hypot || Polyfill.hypot, // backward compat
|
||||
pow: Math.pow,
|
||||
atan2: Math.atan2,
|
||||
"if": Polyfill.condition,
|
||||
gamma: Polyfill.gamma,
|
||||
"Γ": Polyfill.gamma,
|
||||
roundTo: Polyfill.roundTo,
|
||||
map: Polyfill.arrayMap,
|
||||
fold: Polyfill.arrayFold,
|
||||
filter: Polyfill.arrayFilter,
|
||||
indexOf: Polyfill.stringOrArrayIndexOf,
|
||||
join: Polyfill.arrayJoin
|
||||
}
|
||||
|
||||
// These constants will automatically be replaced the MOMENT they are parsed.
|
||||
|
@ -151,6 +159,10 @@ export class Parser {
|
|||
return new ExprEvalExpression(instr, this)
|
||||
}
|
||||
|
||||
evaluate(expr, variables) {
|
||||
return this.parse(expr).evaluate(variables)
|
||||
}
|
||||
|
||||
isOperatorEnabled(op) {
|
||||
const optionName = optionNameMap.hasOwnProperty(op) ? optionNameMap[op] : op
|
||||
const operators = this.options.operators || {}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*
|
||||
* Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu)
|
||||
*
|
||||
* Copyright (c) 2015 Matthew Crumley, 2021-2025 Ad5001
|
||||
* Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*
|
||||
* Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu)
|
||||
*
|
||||
* Copyright (c) 2015 Matthew Crumley, 2021-2025 Ad5001
|
||||
* Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
|
@ -210,8 +210,9 @@ export function gamma(n) {
|
|||
}
|
||||
|
||||
export function stringOrArrayLength(s) {
|
||||
if(Array.isArray(s))
|
||||
if(Array.isArray(s)) {
|
||||
return s.length
|
||||
}
|
||||
return String(s).length
|
||||
}
|
||||
|
||||
|
@ -266,6 +267,11 @@ export function roundTo(value, exp) {
|
|||
return +(value[0] + "e" + (value[1] ? (+value[1] + exp) : exp))
|
||||
}
|
||||
|
||||
export function setVar(name, value, variables) {
|
||||
if(variables) variables[name] = value
|
||||
return value
|
||||
}
|
||||
|
||||
export function arrayIndex(array, index) {
|
||||
return array[index | 0]
|
||||
}
|
||||
|
@ -290,6 +296,58 @@ export function min(array) {
|
|||
}
|
||||
}
|
||||
|
||||
export function arrayMap(f, a) {
|
||||
if(typeof f !== "function") {
|
||||
throw new EvalError(qsTranslate("error", "First argument to map is not a function."))
|
||||
}
|
||||
if(!Array.isArray(a)) {
|
||||
throw new EvalError(qsTranslate("error", "Second argument to map is not an array."))
|
||||
}
|
||||
return a.map(function(x, i) {
|
||||
return f(x, i)
|
||||
})
|
||||
}
|
||||
|
||||
export function arrayFold(f, init, a) {
|
||||
if(typeof f !== "function") {
|
||||
throw new EvalError(qsTranslate("error", "First argument to fold is not a function."))
|
||||
}
|
||||
if(!Array.isArray(a)) {
|
||||
throw new EvalError(qsTranslate("error", "Second argument to fold is not an array."))
|
||||
}
|
||||
return a.reduce(function(acc, x, i) {
|
||||
return f(acc, x, i)
|
||||
}, init)
|
||||
}
|
||||
|
||||
export function arrayFilter(f, a) {
|
||||
if(typeof f !== "function") {
|
||||
throw new EvalError(qsTranslate("error", "First argument to filter is not a function."))
|
||||
}
|
||||
if(!Array.isArray(a)) {
|
||||
throw new EvalError(qsTranslate("error", "Second argument to filter is not an array."))
|
||||
}
|
||||
return a.filter(function(x, i) {
|
||||
return f(x, i)
|
||||
})
|
||||
}
|
||||
|
||||
export function stringOrArrayIndexOf(target, s) {
|
||||
if(!(Array.isArray(s) || typeof s === "string")) {
|
||||
throw new Error(qsTranslate("error", "Second argument to indexOf is not a string or array."))
|
||||
}
|
||||
|
||||
return s.indexOf(target)
|
||||
}
|
||||
|
||||
export function arrayJoin(sep, a) {
|
||||
if(!Array.isArray(a)) {
|
||||
throw new Error(qsTranslate("error", "Second argument to join is not an array."))
|
||||
}
|
||||
|
||||
return a.join(sep)
|
||||
}
|
||||
|
||||
export function sign(x) {
|
||||
return ((x > 0) - (x < 0)) || +x
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*
|
||||
* Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu)
|
||||
*
|
||||
* Copyright (c) 2015 Matthew Crumley, 2021-2025 Ad5001
|
||||
* Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
|
@ -472,7 +472,7 @@ export class TokenStream {
|
|||
this.current = this.newToken(TOP, "==")
|
||||
this.pos++
|
||||
} else {
|
||||
return false
|
||||
this.current = this.newToken(TOP, c)
|
||||
}
|
||||
} else if(c === "!") {
|
||||
if(this.expression.charAt(this.pos + 1) === "=") {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*!
|
||||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -64,20 +64,10 @@ function arrayFlatMap(callbackFn, thisArg) {
|
|||
* @return {String}
|
||||
*/
|
||||
function stringReplaceAll(from, to) {
|
||||
return this.split(from).join(to)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of an element of the array at a given index.
|
||||
* Accepts negative indexes.
|
||||
* @this {Array|string}
|
||||
* @param {number} index
|
||||
* @return {*}
|
||||
*/
|
||||
function arrayAt(index) {
|
||||
if(typeof index !== "number")
|
||||
throw new Error(`${index} is not a number`)
|
||||
return index >= 0 ? this[index] : this[this.length + index]
|
||||
let str = this
|
||||
while(str.includes(from))
|
||||
str = str.replace(from, to)
|
||||
return str
|
||||
}
|
||||
|
||||
|
||||
|
@ -108,8 +98,8 @@ const polyfills = {
|
|||
[String.prototype, "replaceAll", stringReplaceAll]
|
||||
],
|
||||
2022: [
|
||||
[Array.prototype, "at", arrayAt],
|
||||
[String.prototype, "at", arrayAt],
|
||||
[Array.prototype, "at", notPolyfilled("Array.prototype.at")],
|
||||
[String.prototype, "at", notPolyfilled("String.prototype.at")],
|
||||
[Object, "hasOwn", notPolyfilled("Object.hasOwn")]
|
||||
],
|
||||
2023: [
|
||||
|
@ -119,7 +109,7 @@ const polyfills = {
|
|||
[Array.prototype, "toSpliced", notPolyfilled("Array.prototype.toSpliced")],
|
||||
[Array.prototype, "with", notPolyfilled("Array.prototype.with")]
|
||||
],
|
||||
2025: [
|
||||
2024: [
|
||||
[Object, "groupBy", notPolyfilled("Object.groupBy")],
|
||||
[Map, "groupBy", notPolyfilled("Map.groupBy")]
|
||||
]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -24,7 +24,6 @@ import { Expression, executeExpression } from "./expression.mjs"
|
|||
*/
|
||||
export class Domain {
|
||||
constructor() {
|
||||
this.latexMarkup = "#INVALID"
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -207,8 +206,8 @@ export class Range extends Domain {
|
|||
}
|
||||
|
||||
includes(x) {
|
||||
if(typeof x == "string") x = executeExpression(x)
|
||||
if(x instanceof Expression) x = x.execute()
|
||||
if(typeof x == "string") x = executeExpression(x)
|
||||
return ((this.openBegin && x > this.begin.execute()) || (!this.openBegin && x >= this.begin.execute())) &&
|
||||
((this.openEnd && x < this.end.execute()) || (!this.openEnd && x <= this.end.execute()))
|
||||
}
|
||||
|
@ -250,17 +249,15 @@ export class SpecialDomain extends Domain {
|
|||
/**
|
||||
* @constructs SpecialDomain
|
||||
* @param {string} displayName
|
||||
* @param {string} latexMarkup - markup representing the domain.
|
||||
* @param {function} isValid - function returning true when number is in domain false when it isn't.
|
||||
* @param {function} next - function provides the next positive value in the domain after the one given.
|
||||
* @param {function} previous - function provides the previous positive value in the domain before the one given.
|
||||
* @param {boolean} moveSupported - Only true if next and previous functions are valid.
|
||||
*/
|
||||
constructor(displayName, latexMarkup, isValid, next = () => true, previous = () => true,
|
||||
constructor(displayName, isValid, next = () => true, previous = () => true,
|
||||
moveSupported = true) {
|
||||
super()
|
||||
this.displayName = displayName
|
||||
this.latexMarkup = latexMarkup
|
||||
this.isValid = isValid
|
||||
this.nextValue = next
|
||||
this.prevValue = previous
|
||||
|
@ -565,54 +562,39 @@ Domain.RPE.latexMarkup = "\\mathbb{R}^{+*}"
|
|||
Domain.RME = new Range(-Infinity, 0, true, true)
|
||||
Domain.RME.displayName = "ℝ⁻*"
|
||||
Domain.RME.latexMarkup = "\\mathbb{R}^{+*}"
|
||||
Domain.N = new SpecialDomain(
|
||||
"ℕ", "\\mathbb{N}",
|
||||
x => x % 1 === 0 && x >= 0,
|
||||
Domain.N = new SpecialDomain("ℕ", x => x % 1 === 0 && x >= 0,
|
||||
x => Math.max(Math.floor(x) + 1, 0),
|
||||
x => Math.max(Math.ceil(x) - 1, 0)
|
||||
)
|
||||
Domain.NE = new SpecialDomain(
|
||||
"ℕ*", "\\mathbb{N}^{*}",
|
||||
x => x % 1 === 0 && x > 0,
|
||||
x => Math.max(Math.ceil(x) - 1, 0))
|
||||
Domain.N.latexMarkup = "\\mathbb{N}"
|
||||
Domain.NE = new SpecialDomain("ℕ*", x => x % 1 === 0 && x > 0,
|
||||
x => Math.max(Math.floor(x) + 1, 1),
|
||||
x => Math.max(Math.ceil(x) - 1, 1)
|
||||
)
|
||||
Domain.Z = new SpecialDomain(
|
||||
"ℤ", "\\mathbb{Z}",
|
||||
x => x % 1 === 0,
|
||||
x => Math.floor(x) + 1,
|
||||
x => Math.ceil(x) - 1
|
||||
)
|
||||
Domain.ZE = new SpecialDomain(
|
||||
"ℤ*", "\\mathbb{Z}^{*}",
|
||||
x => x % 1 === 0 && x !== 0,
|
||||
x => Math.max(Math.ceil(x) - 1, 1))
|
||||
Domain.NE.latexMarkup = "\\mathbb{N}^{*}"
|
||||
Domain.Z = new SpecialDomain("ℤ", x => x % 1 === 0, x => Math.floor(x) + 1, x => Math.ceil(x) - 1)
|
||||
Domain.Z.latexMarkup = "\\mathbb{Z}"
|
||||
Domain.ZE = new SpecialDomain("ℤ*", x => x % 1 === 0 && x !== 0,
|
||||
x => Math.floor(x) + 1 === 0 ? Math.floor(x) + 2 : Math.floor(x) + 1,
|
||||
x => Math.ceil(x) - 1 === 0 ? Math.ceil(x) - 2 : Math.ceil(x) - 1
|
||||
)
|
||||
Domain.ZM = new SpecialDomain(
|
||||
"ℤ⁻", "\\mathbb{Z}^{-}",
|
||||
x => x % 1 === 0 && x <= 0,
|
||||
x => Math.ceil(x) - 1 === 0 ? Math.ceil(x) - 2 : Math.ceil(x) - 1)
|
||||
Domain.ZE.latexMarkup = "\\mathbb{Z}^{*}"
|
||||
Domain.ZM = new SpecialDomain("ℤ⁻", x => x % 1 === 0 && x <= 0,
|
||||
x => Math.min(Math.floor(x) + 1, 0),
|
||||
x => Math.min(Math.ceil(x) - 1, 0)
|
||||
)
|
||||
Domain.ZME = new SpecialDomain(
|
||||
"ℤ⁻*", "\\mathbb{Z}^{-*}",
|
||||
x => x % 1 === 0 && x < 0,
|
||||
x => Math.min(Math.ceil(x) - 1, 0))
|
||||
Domain.ZM.latexMarkup = "\\mathbb{Z}^{-}"
|
||||
Domain.ZME = new SpecialDomain("ℤ⁻*", x => x % 1 === 0 && x < 0,
|
||||
x => Math.min(Math.floor(x) + 1, -1),
|
||||
x => Math.min(Math.ceil(x) - 1, -1)
|
||||
)
|
||||
Domain.NLog = new SpecialDomain(
|
||||
"ℕˡᵒᵍ", "\\mathbb{N}^{log}",
|
||||
x => Math.min(Math.ceil(x) - 1, -1))
|
||||
Domain.ZME.latexMarkup = "\\mathbb{Z}^{-*}"
|
||||
Domain.NLog = new SpecialDomain("ℕˡᵒᵍ",
|
||||
x => x / Math.pow(10, Math.ceil(Math.log10(x))) % 1 === 0 && x > 0,
|
||||
x => {
|
||||
function(x) {
|
||||
let x10pow = Math.pow(10, Math.ceil(Math.log10(x)))
|
||||
return Math.max(1, (Math.floor(x / x10pow) + 1) * x10pow)
|
||||
},
|
||||
x => {
|
||||
function(x) {
|
||||
let x10pow = Math.pow(10, Math.ceil(Math.log10(x)))
|
||||
return Math.max(1, (Math.ceil(x / x10pow) - 1) * x10pow)
|
||||
}
|
||||
)
|
||||
})
|
||||
Domain.NLog.latexMarkup = "\\mathbb{N}^{log}"
|
||||
|
||||
let refedDomains = []
|
||||
|
||||
|
@ -644,7 +626,7 @@ export function parseDomainSimple(domain) {
|
|||
if(domain.includes("U") || domain.includes("∪")) return UnionDomain.import(domain)
|
||||
if(domain.includes("∩")) return IntersectionDomain.import(domain)
|
||||
if(domain.includes("∖") || domain.includes("\\")) return MinusDomain.import(domain)
|
||||
if(domain.at(0) === "{" && domain.at(-1) === "}") return DomainSet.import(domain)
|
||||
if(domain.charAt(0) === "{" && domain.charAt(domain.length - 1) === "}") return DomainSet.import(domain)
|
||||
if(domain.includes("]") || domain.includes("[")) return Range.import(domain)
|
||||
if(["R", "ℝ", "N", "ℕ", "Z", "ℤ"].some(str => domain.toUpperCase().includes(str)))
|
||||
return Domain.import(domain)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -17,38 +17,28 @@
|
|||
*/
|
||||
|
||||
|
||||
import * as Utils from "../utils/index.mjs"
|
||||
import { ExprEvalExpression } from "../lib/expr-eval/expression.mjs"
|
||||
import * as Utils from "../utils.mjs"
|
||||
import Latex from "../module/latex.mjs"
|
||||
import ExprParser from "../module/expreval.mjs"
|
||||
import Objects from "../module/objects.mjs"
|
||||
|
||||
const NUMBER_MATCHER = /^\d*\.\d+(e[+-]\d+)?$/
|
||||
|
||||
/**
|
||||
* Represents any kind of x-based or non variable based expression.
|
||||
*/
|
||||
export class Expression {
|
||||
/**
|
||||
*
|
||||
* @param {string|ExprEvalExpression} expr
|
||||
*/
|
||||
constructor(expr) {
|
||||
if(typeof expr === "string") {
|
||||
this.expr = Utils.exponentsToExpression(expr)
|
||||
this.calc = ExprParser.parse(this.expr).simplify()
|
||||
} else if(expr instanceof ExprEvalExpression) {
|
||||
} else {
|
||||
// Passed an expression here directly.
|
||||
this.calc = expr.simplify()
|
||||
this.expr = expr.toString()
|
||||
} else {
|
||||
const type = expr != null ? "a " + expr.constructor.name : expr
|
||||
throw new Error(`Cannot create an expression with ${type}.`)
|
||||
}
|
||||
this.canBeCached = this.isConstant()
|
||||
this.cached = this.isConstant()
|
||||
this.cachedValue = null
|
||||
if(this.canBeCached && this.allRequirementsFulfilled())
|
||||
this.recache()
|
||||
if(this.cached && this.allRequirementsFulfilled())
|
||||
this.cachedValue = this.calc.evaluate(Objects.currentObjectsByName)
|
||||
this.latexMarkup = Latex.expression(this.calc.tokens)
|
||||
}
|
||||
|
||||
|
@ -87,20 +77,21 @@ export class Expression {
|
|||
|
||||
/**
|
||||
* Returns a list of names whose corresponding objects this expression is dependant on and are missing.
|
||||
* @return {string[]}
|
||||
* @return {boolean}
|
||||
*/
|
||||
undefinedVariables() {
|
||||
return this.requiredObjects().filter(objName => !(objName in Objects.currentObjectsByName))
|
||||
}
|
||||
|
||||
recache() {
|
||||
if(this.cached)
|
||||
this.cachedValue = this.calc.evaluate(Objects.currentObjectsByName)
|
||||
}
|
||||
|
||||
execute(x = 1) {
|
||||
if(this.canBeCached) {
|
||||
if(this.cached) {
|
||||
if(this.cachedValue == null)
|
||||
this.recache()
|
||||
this.cachedValue = this.calc.evaluate(Objects.currentObjectsByName)
|
||||
return this.cachedValue
|
||||
}
|
||||
ExprParser.currentVars = Object.assign({ "x": x }, Objects.currentObjectsByName)
|
||||
|
@ -108,10 +99,9 @@ export class Expression {
|
|||
}
|
||||
|
||||
simplify(x) {
|
||||
let expr = new Expression(this.calc.substitute("x", x).simplify())
|
||||
if(expr.allRequirementsFulfilled() && expr.execute() === 0)
|
||||
expr = new Expression("0")
|
||||
return expr
|
||||
let expr = this.calc.substitute("x", x).simplify()
|
||||
if(expr.evaluate() === 0) expr = "0"
|
||||
return new Expression(expr)
|
||||
}
|
||||
|
||||
toEditableString() {
|
||||
|
@ -120,28 +110,17 @@ export class Expression {
|
|||
|
||||
toString(forceSign = false) {
|
||||
let str = Utils.makeExpressionReadable(this.calc.toString())
|
||||
if(str !== undefined && str.match(NUMBER_MATCHER)) {
|
||||
const decimals = str.split(".")[1].split("e")[0]
|
||||
const zeros = decimals.split("0").length
|
||||
const nines = decimals.split("9").length
|
||||
if(zeros > 7 || nines > 7) {
|
||||
if(str !== undefined && str.match(/^\d*\.\d+$/)) {
|
||||
if(str.split(".")[1].split("0").length > 7) {
|
||||
// Likely rounding error
|
||||
str = parseFloat(str).toDecimalPrecision(8).toString()
|
||||
str = parseFloat(str.substring(0, str.length - 1)).toString()
|
||||
}
|
||||
}
|
||||
if(str[0] === "(" && str.at(-1) === ")")
|
||||
str = str.substring(1, str.length - 1)
|
||||
if(str[0] !== "-" && forceSign)
|
||||
str = "+" + str
|
||||
if(str[0] !== "-" && forceSign) str = "+" + str
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses and executes the given expression
|
||||
* @param {string} expr
|
||||
* @return {number}
|
||||
*/
|
||||
export function executeExpression(expr) {
|
||||
return (new Expression(expr.toString())).execute()
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -17,7 +17,7 @@
|
|||
*/
|
||||
|
||||
import * as Expr from "./expression.mjs"
|
||||
import * as Utils from "../utils/index.mjs"
|
||||
import * as Utils from "../utils.mjs"
|
||||
import Latex from "../module/latex.mjs"
|
||||
import Objects from "../module/objects.mjs"
|
||||
import ExprParser from "../module/expreval.mjs"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -18,33 +18,30 @@
|
|||
|
||||
import { Module } from "./common.mjs"
|
||||
import { CanvasInterface, DialogInterface } from "./interface.mjs"
|
||||
import { textsup } from "../utils/index.mjs"
|
||||
import { textsup } from "../utils.mjs"
|
||||
import { Expression } from "../math/index.mjs"
|
||||
import Latex from "./latex.mjs"
|
||||
import Objects from "./objects.mjs"
|
||||
import History from "./history.mjs"
|
||||
import Settings from "./settings.mjs"
|
||||
|
||||
|
||||
class CanvasAPI extends Module {
|
||||
|
||||
|
||||
/** @type {CanvasInterface} */
|
||||
#canvas = null
|
||||
/** @type {CanvasRenderingContext2D} */
|
||||
#ctx = null
|
||||
/** Lock to prevent asynchronous stuff from printing stuff that is outdated. */
|
||||
#redrawCount = 0
|
||||
/** @type {{show(string, string, string)}} */
|
||||
#drawingErrorDialog = null
|
||||
|
||||
|
||||
constructor() {
|
||||
super("Canvas", {
|
||||
canvas: CanvasInterface,
|
||||
drawingErrorDialog: DialogInterface
|
||||
})
|
||||
|
||||
/** @type {CanvasInterface} */
|
||||
this._canvas = null
|
||||
|
||||
/** @type {CanvasRenderingContext2D} */
|
||||
this._ctx = null
|
||||
|
||||
/**
|
||||
* @type {{show(string, string, string)}}
|
||||
* @private
|
||||
*/
|
||||
this._drawingErrorDialog = null
|
||||
/**
|
||||
*
|
||||
* @type {Object.<string, {expression: Expression, value: number, maxDraw: number}>}
|
||||
|
@ -70,18 +67,18 @@ class CanvasAPI extends Module {
|
|||
*/
|
||||
initialize({ canvas, drawingErrorDialog }) {
|
||||
super.initialize({ canvas, drawingErrorDialog })
|
||||
this.#canvas = canvas
|
||||
this.#drawingErrorDialog = drawingErrorDialog
|
||||
this._canvas = canvas
|
||||
this._drawingErrorDialog = drawingErrorDialog
|
||||
}
|
||||
|
||||
get width() {
|
||||
if(!this.initialized) throw new Error("Attempting width before initialize!")
|
||||
return this.#canvas.width
|
||||
return this._canvas.width
|
||||
}
|
||||
|
||||
get height() {
|
||||
if(!this.initialized) throw new Error("Attempting height before initialize!")
|
||||
return this.#canvas.height
|
||||
return this._canvas.height
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -90,7 +87,7 @@ class CanvasAPI extends Module {
|
|||
*/
|
||||
get xmin() {
|
||||
if(!this.initialized) throw new Error("Attempting xmin before initialize!")
|
||||
return Settings.xmin
|
||||
return this._canvas.xmin
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -99,7 +96,7 @@ class CanvasAPI extends Module {
|
|||
*/
|
||||
get xzoom() {
|
||||
if(!this.initialized) throw new Error("Attempting xzoom before initialize!")
|
||||
return Settings.xzoom
|
||||
return this._canvas.xzoom
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -108,7 +105,7 @@ class CanvasAPI extends Module {
|
|||
*/
|
||||
get ymax() {
|
||||
if(!this.initialized) throw new Error("Attempting ymax before initialize!")
|
||||
return Settings.ymax
|
||||
return this._canvas.ymax
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -117,7 +114,7 @@ class CanvasAPI extends Module {
|
|||
*/
|
||||
get yzoom() {
|
||||
if(!this.initialized) throw new Error("Attempting yzoom before initialize!")
|
||||
return Settings.yzoom
|
||||
return this._canvas.yzoom
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -126,7 +123,7 @@ class CanvasAPI extends Module {
|
|||
*/
|
||||
get xlabel() {
|
||||
if(!this.initialized) throw new Error("Attempting xlabel before initialize!")
|
||||
return Settings.xlabel
|
||||
return this._canvas.xlabel
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -135,7 +132,7 @@ class CanvasAPI extends Module {
|
|||
*/
|
||||
get ylabel() {
|
||||
if(!this.initialized) throw new Error("Attempting ylabel before initialize!")
|
||||
return Settings.ylabel
|
||||
return this._canvas.ylabel
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -144,7 +141,7 @@ class CanvasAPI extends Module {
|
|||
*/
|
||||
get linewidth() {
|
||||
if(!this.initialized) throw new Error("Attempting linewidth before initialize!")
|
||||
return Settings.linewidth
|
||||
return this._canvas.linewidth
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -153,7 +150,7 @@ class CanvasAPI extends Module {
|
|||
*/
|
||||
get textsize() {
|
||||
if(!this.initialized) throw new Error("Attempting textsize before initialize!")
|
||||
return Settings.textsize
|
||||
return this._canvas.textsize
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -162,7 +159,7 @@ class CanvasAPI extends Module {
|
|||
*/
|
||||
get logscalex() {
|
||||
if(!this.initialized) throw new Error("Attempting logscalex before initialize!")
|
||||
return Settings.logscalex
|
||||
return this._canvas.logscalex
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -171,7 +168,7 @@ class CanvasAPI extends Module {
|
|||
*/
|
||||
get showxgrad() {
|
||||
if(!this.initialized) throw new Error("Attempting showxgrad before initialize!")
|
||||
return Settings.showxgrad
|
||||
return this._canvas.showxgrad
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -180,7 +177,7 @@ class CanvasAPI extends Module {
|
|||
*/
|
||||
get showygrad() {
|
||||
if(!this.initialized) throw new Error("Attempting showygrad before initialize!")
|
||||
return Settings.showygrad
|
||||
return this._canvas.showygrad
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -204,7 +201,7 @@ class CanvasAPI extends Module {
|
|||
|
||||
requestPaint() {
|
||||
if(!this.initialized) throw new Error("Attempting requestPaint before initialize!")
|
||||
this.#canvas.requestPaint()
|
||||
this._canvas.requestPaint()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -212,19 +209,17 @@ class CanvasAPI extends Module {
|
|||
*/
|
||||
redraw() {
|
||||
if(!this.initialized) throw new Error("Attempting redraw before initialize!")
|
||||
if(this.#ctx == null)
|
||||
this.#ctx = this.#canvas.getContext("2d")
|
||||
this.#redrawCount = (this.#redrawCount + 1) % 10000
|
||||
this._ctx = this._canvas.getContext("2d")
|
||||
this._computeAxes()
|
||||
this._reset()
|
||||
this._drawGrid()
|
||||
this._drawAxes()
|
||||
this._drawLabels()
|
||||
this.#ctx.lineWidth = this.linewidth
|
||||
this._ctx.lineWidth = this.linewidth
|
||||
for(let objType in Objects.currentObjects) {
|
||||
for(let obj of Objects.currentObjects[objType]) {
|
||||
this.#ctx.strokeStyle = obj.color
|
||||
this.#ctx.fillStyle = obj.color
|
||||
this._ctx.strokeStyle = obj.color
|
||||
this._ctx.fillStyle = obj.color
|
||||
if(obj.visible)
|
||||
try {
|
||||
obj.draw(this)
|
||||
|
@ -232,12 +227,12 @@ class CanvasAPI extends Module {
|
|||
// Drawing throws an error. Generally, it's due to a new modification (or the opening of a file)
|
||||
console.error(e)
|
||||
console.log(e.stack)
|
||||
this.#drawingErrorDialog.show(objType, obj.name, e.message)
|
||||
this._drawingErrorDialog.show(objType, obj.name, e.message)
|
||||
History.undo()
|
||||
}
|
||||
}
|
||||
}
|
||||
this.#ctx.lineWidth = 1
|
||||
this._ctx.lineWidth = 1
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -245,9 +240,9 @@ class CanvasAPI extends Module {
|
|||
* @private
|
||||
*/
|
||||
_computeAxes() {
|
||||
let exprY = new Expression(`x*(${Settings.yaxisstep})`)
|
||||
let exprY = new Expression(`x*(${this._canvas.yaxisstep})`)
|
||||
let y1 = exprY.execute(1)
|
||||
let exprX = new Expression(`x*(${Settings.xaxisstep})`)
|
||||
let exprX = new Expression(`x*(${this._canvas.xaxisstep})`)
|
||||
let x1 = exprX.execute(1)
|
||||
this.axesSteps = {
|
||||
x: {
|
||||
|
@ -269,10 +264,10 @@ class CanvasAPI extends Module {
|
|||
*/
|
||||
_reset() {
|
||||
// Reset
|
||||
this.#ctx.fillStyle = "#FFFFFF"
|
||||
this.#ctx.strokeStyle = "#000000"
|
||||
this.#ctx.font = `${this.textsize}px sans-serif`
|
||||
this.#ctx.fillRect(0, 0, this.width, this.height)
|
||||
this._ctx.fillStyle = "#FFFFFF"
|
||||
this._ctx.strokeStyle = "#000000"
|
||||
this._ctx.font = `${this.textsize}px sans-serif`
|
||||
this._ctx.fillRect(0, 0, this.width, this.height)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -280,7 +275,7 @@ class CanvasAPI extends Module {
|
|||
* @private
|
||||
*/
|
||||
_drawGrid() {
|
||||
this.#ctx.strokeStyle = "#C0C0C0"
|
||||
this._ctx.strokeStyle = "#C0C0C0"
|
||||
if(this.logscalex) {
|
||||
for(let xpow = -this.maxgradx; xpow <= this.maxgradx; xpow++) {
|
||||
for(let xmulti = 1; xmulti < 10; xmulti++) {
|
||||
|
@ -304,7 +299,7 @@ class CanvasAPI extends Module {
|
|||
* @private
|
||||
*/
|
||||
_drawAxes() {
|
||||
this.#ctx.strokeStyle = "#000000"
|
||||
this._ctx.strokeStyle = "#000000"
|
||||
let axisypos = this.logscalex ? 1 : 0
|
||||
this.drawXLine(axisypos)
|
||||
this.drawYLine(0)
|
||||
|
@ -325,19 +320,19 @@ class CanvasAPI extends Module {
|
|||
let axisypx = this.x2px(this.logscalex ? 1 : 0) // X coordinate of Y axis
|
||||
let axisxpx = this.y2px(0) // Y coordinate of X axis
|
||||
// Labels
|
||||
this.#ctx.fillStyle = "#000000"
|
||||
this.#ctx.font = `${this.textsize}px sans-serif`
|
||||
this.#ctx.fillText(this.ylabel, axisypx + 10, 24)
|
||||
let textWidth = this.#ctx.measureText(this.xlabel).width
|
||||
this.#ctx.fillText(this.xlabel, this.width - 14 - textWidth, axisxpx - 5)
|
||||
this._ctx.fillStyle = "#000000"
|
||||
this._ctx.font = `${this.textsize}px sans-serif`
|
||||
this._ctx.fillText(this.ylabel, axisypx + 10, 24)
|
||||
let textWidth = this._ctx.measureText(this.xlabel).width
|
||||
this._ctx.fillText(this.xlabel, this.width - 14 - textWidth, axisxpx - 5)
|
||||
// Axis graduation labels
|
||||
this.#ctx.font = `${this.textsize - 4}px sans-serif`
|
||||
this._ctx.font = `${this.textsize - 4}px sans-serif`
|
||||
|
||||
let txtMinus = this.#ctx.measureText("-").width
|
||||
let txtMinus = this._ctx.measureText("-").width
|
||||
if(this.showxgrad) {
|
||||
if(this.logscalex) {
|
||||
for(let xpow = -this.maxgradx; xpow <= this.maxgradx; xpow += 1) {
|
||||
textWidth = this.#ctx.measureText("10" + textsup(xpow)).width
|
||||
textWidth = this._ctx.measureText("10" + textsup(xpow)).width
|
||||
if(xpow !== 0)
|
||||
this.drawVisibleText("10" + textsup(xpow), this.x2px(Math.pow(10, xpow)) - textWidth / 2, axisxpx + 16 + (6 * (xpow === 1)))
|
||||
}
|
||||
|
@ -355,13 +350,13 @@ class CanvasAPI extends Module {
|
|||
for(let y = 0; y < this.axesSteps.y.maxDraw; y += 1) {
|
||||
let drawY = y * this.axesSteps.y.value
|
||||
let txtY = this.axesSteps.y.expression.simplify(y).toString().replace(/^\((.+)\)$/, "$1")
|
||||
textWidth = this.#ctx.measureText(txtY).width
|
||||
textWidth = this._ctx.measureText(txtY).width
|
||||
this.drawVisibleText(txtY, axisypx - 6 - textWidth, this.y2px(drawY) + 4 + (10 * (y === 0)))
|
||||
if(y !== 0)
|
||||
this.drawVisibleText("-" + txtY, axisypx - 6 - textWidth - txtMinus, this.y2px(-drawY) + 4)
|
||||
}
|
||||
}
|
||||
this.#ctx.fillStyle = "#FFFFFF"
|
||||
this._ctx.fillStyle = "#FFFFFF"
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -399,7 +394,7 @@ class CanvasAPI extends Module {
|
|||
drawVisibleText(text, x, y) {
|
||||
if(x > 0 && x < this.width && y > 0 && y < this.height) {
|
||||
text.toString().split("\n").forEach((txt, i) => {
|
||||
this.#ctx.fillText(txt, x, y + (this.textsize * i))
|
||||
this._ctx.fillText(txt, x, y + (this.textsize * i))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -414,8 +409,8 @@ class CanvasAPI extends Module {
|
|||
* @param {number} height
|
||||
*/
|
||||
drawVisibleImage(image, x, y, width, height) {
|
||||
this.#canvas.markDirty(Qt.rect(x, y, width, height))
|
||||
this.#ctx.drawImage(image, x, y, width, height)
|
||||
this._canvas.markDirty(Qt.rect(x, y, width, height))
|
||||
this._ctx.drawImage(image, x, y, width, height)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -429,7 +424,7 @@ class CanvasAPI extends Module {
|
|||
let defaultHeight = this.textsize * 1.2 // Approximate but good enough!
|
||||
for(let txt of text.split("\n")) {
|
||||
theight += defaultHeight
|
||||
if(this.#ctx.measureText(txt).width > twidth) twidth = this.#ctx.measureText(txt).width
|
||||
if(this._ctx.measureText(txt).width > twidth) twidth = this._ctx.measureText(txt).width
|
||||
}
|
||||
return { "width": twidth, "height": theight }
|
||||
}
|
||||
|
@ -499,10 +494,10 @@ class CanvasAPI extends Module {
|
|||
* @param {number} y2
|
||||
*/
|
||||
drawLine(x1, y1, x2, y2) {
|
||||
this.#ctx.beginPath()
|
||||
this.#ctx.moveTo(x1, y1)
|
||||
this.#ctx.lineTo(x2, y2)
|
||||
this.#ctx.stroke()
|
||||
this._ctx.beginPath()
|
||||
this._ctx.moveTo(x1, y1)
|
||||
this._ctx.lineTo(x2, y2)
|
||||
this._ctx.stroke()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -514,9 +509,9 @@ class CanvasAPI extends Module {
|
|||
* @param {number} dashPxSize
|
||||
*/
|
||||
drawDashedLine(x1, y1, x2, y2, dashPxSize = 6) {
|
||||
this.#ctx.setLineDash([dashPxSize / 2, dashPxSize])
|
||||
this._ctx.setLineDash([dashPxSize / 2, dashPxSize])
|
||||
this.drawLine(x1, y1, x2, y2)
|
||||
this.#ctx.setLineDash([])
|
||||
this._ctx.setLineDash([])
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -526,22 +521,14 @@ class CanvasAPI extends Module {
|
|||
* @param {function(LatexRenderResult|{width: number, height: number, source: string})} callback
|
||||
*/
|
||||
renderLatexImage(ltxText, color, callback) {
|
||||
const currentRedrawCount = this.#redrawCount
|
||||
const onRendered = (imgData) => {
|
||||
if(!this.#canvas.isImageLoaded(imgData.source) && !this.#canvas.isImageLoading(imgData.source)) {
|
||||
if(!this._canvas.isImageLoaded(imgData.source) && !this._canvas.isImageLoading(imgData.source)) {
|
||||
// Wait until the image is loaded to callback.
|
||||
this.#canvas.loadImageAsync(imgData.source).then(() => {
|
||||
if(this.#redrawCount === currentRedrawCount)
|
||||
callback(imgData)
|
||||
else
|
||||
console.log("1. Discard render of", imgData.source, this.#redrawCount, currentRedrawCount)
|
||||
})
|
||||
this._canvas.loadImage(imgData.source)
|
||||
this._canvas.imageLoaders[imgData.source] = [callback, imgData]
|
||||
} else {
|
||||
// Callback directly
|
||||
if(this.#redrawCount === currentRedrawCount)
|
||||
callback(imgData)
|
||||
else
|
||||
console.log("2. Discard render of", imgData.source, this.#redrawCount, currentRedrawCount)
|
||||
}
|
||||
}
|
||||
const prerendered = Latex.findPrerendered(ltxText, this.textsize, color)
|
||||
|
@ -556,11 +543,11 @@ class CanvasAPI extends Module {
|
|||
//
|
||||
|
||||
get font() {
|
||||
return this.#ctx.font
|
||||
return this._ctx.font
|
||||
}
|
||||
|
||||
set font(value) {
|
||||
return this.#ctx.font = value
|
||||
return this._ctx.font = value
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -573,9 +560,9 @@ class CanvasAPI extends Module {
|
|||
* @param {boolean} counterclockwise
|
||||
*/
|
||||
arc(x, y, radius, startAngle, endAngle, counterclockwise = false) {
|
||||
this.#ctx.beginPath()
|
||||
this.#ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise)
|
||||
this.#ctx.stroke()
|
||||
this._ctx.beginPath()
|
||||
this._ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise)
|
||||
this._ctx.stroke()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -585,9 +572,9 @@ class CanvasAPI extends Module {
|
|||
* @param {number} radius
|
||||
*/
|
||||
disc(x, y, radius) {
|
||||
this.#ctx.beginPath()
|
||||
this.#ctx.arc(x, y, radius, 0, 2 * Math.PI)
|
||||
this.#ctx.fill()
|
||||
this._ctx.beginPath()
|
||||
this._ctx.arc(x, y, radius, 0, 2 * Math.PI)
|
||||
this._ctx.fill()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -598,7 +585,7 @@ class CanvasAPI extends Module {
|
|||
* @param {number} h
|
||||
*/
|
||||
fillRect(x, y, w, h) {
|
||||
this.#ctx.fillRect(x, y, w, h)
|
||||
this._ctx.fillRect(x, y, w, h)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -17,7 +17,6 @@
|
|||
*/
|
||||
|
||||
import { Interface } from "./interface.mjs"
|
||||
import { BaseEventEmitter } from "../events.mjs"
|
||||
|
||||
// Define Modules interface before they are imported.
|
||||
globalThis.Modules = globalThis.Modules || {}
|
||||
|
@ -25,13 +24,7 @@ globalThis.Modules = globalThis.Modules || {}
|
|||
/**
|
||||
* Base class for global APIs in runtime.
|
||||
*/
|
||||
export class Module extends BaseEventEmitter {
|
||||
/** @type {string} */
|
||||
#name
|
||||
/** @type {Object.<string, (Interface|string|number|boolean)>} */
|
||||
#initializationParameters
|
||||
/** @type {boolean} */
|
||||
#initialized = false
|
||||
export class Module {
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -39,18 +32,11 @@ export class Module extends BaseEventEmitter {
|
|||
* @param {Object.<string, (Interface|string|number|boolean)>} initializationParameters - List of parameters for the initialize function.
|
||||
*/
|
||||
constructor(name, initializationParameters = {}) {
|
||||
super()
|
||||
console.log(`Loading module ${name}...`)
|
||||
this.#name = name
|
||||
this.#initializationParameters = initializationParameters
|
||||
}
|
||||
this.__name = name
|
||||
this.__initializationParameters = initializationParameters
|
||||
this.initialized = false
|
||||
|
||||
get name() {
|
||||
return this.#name;
|
||||
}
|
||||
|
||||
get initialized() {
|
||||
return this.#initialized
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -58,17 +44,17 @@ export class Module extends BaseEventEmitter {
|
|||
* @param {Object.<string, any>} options
|
||||
*/
|
||||
initialize(options) {
|
||||
if(this.#initialized)
|
||||
throw new Error(`Cannot reinitialize module ${this.#name}.`)
|
||||
console.log(`Initializing ${this.#name}...`)
|
||||
for(const [name, value] of Object.entries(this.#initializationParameters)) {
|
||||
if(this.initialized)
|
||||
throw new Error(`Cannot reinitialize module ${this.__name}.`)
|
||||
console.log(`Initializing ${this.__name}...`)
|
||||
for(const [name, value] of Object.entries(this.__initializationParameters)) {
|
||||
if(!options.hasOwnProperty(name))
|
||||
throw new Error(`Option '${name}' of initialize of module ${this.#name} does not exist.`)
|
||||
throw new Error(`Option '${name}' of initialize of module ${this.__name} does not exist.`)
|
||||
if(typeof value === "function" && value.prototype instanceof Interface)
|
||||
Interface.checkImplementation(value, options[name])
|
||||
Interface.check_implementation(value, options[name])
|
||||
else if(typeof value !== typeof options[name])
|
||||
throw new Error(`Option '${name}' of initialize of module ${this.#name} is not a '${value}' (${typeof options[name]}).`)
|
||||
throw new Error(`Option '${name}' of initialize of module ${this.__name} is not a '${value}' (${typeof options[name]}).`)
|
||||
}
|
||||
this.#initialized = true
|
||||
this.initialized = true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -19,7 +19,7 @@
|
|||
import { Module } from "./common.mjs"
|
||||
import { Parser } from "../lib/expr-eval/parser.mjs"
|
||||
|
||||
const EVAL_VARIABLES = {
|
||||
const evalVariables = {
|
||||
// Variables not provided by expr-eval.js, needs to be provided manually
|
||||
"pi": Math.PI,
|
||||
"PI": Math.PI,
|
||||
|
@ -35,17 +35,15 @@ const EVAL_VARIABLES = {
|
|||
}
|
||||
|
||||
class ExprParserAPI extends Module {
|
||||
#parser = new Parser()
|
||||
|
||||
constructor() {
|
||||
super("ExprParser")
|
||||
this.currentVars = {}
|
||||
this.#parser = new Parser()
|
||||
this._parser = new Parser()
|
||||
|
||||
this.#parser.consts = Object.assign({}, this.#parser.consts, EVAL_VARIABLES)
|
||||
this._parser.consts = Object.assign({}, this._parser.consts, evalVariables)
|
||||
|
||||
this.#parser.functions.integral = this.integral.bind(this)
|
||||
this.#parser.functions.derivative = this.derivative.bind(this)
|
||||
this._parser.functions.integral = this.integral.bind(this)
|
||||
this._parser.functions.derivative = this.derivative.bind(this)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -70,7 +68,7 @@ class ExprParserAPI extends Module {
|
|||
[f, variable] = args
|
||||
if(typeof f !== "string" || typeof variable !== "string")
|
||||
throw EvalError(qsTranslate("usage", "Usage:\n%1").arg(usage2))
|
||||
f = this.#parser.parse(f).toJSFunction(variable, this.currentVars)
|
||||
f = this._parser.parse(f).toJSFunction(variable, this.currentVars)
|
||||
} else
|
||||
throw EvalError(qsTranslate("usage", "Usage:\n%1\n%2").arg(usage1).arg(usage2))
|
||||
return f
|
||||
|
@ -81,14 +79,14 @@ class ExprParserAPI extends Module {
|
|||
* @returns {ExprEvalExpression}
|
||||
*/
|
||||
parse(expression) {
|
||||
return this.#parser.parse(expression)
|
||||
return this._parser.parse(expression)
|
||||
}
|
||||
|
||||
integral(a = null, b = null, ...args) {
|
||||
integral(a, b, ...args) {
|
||||
let usage1 = qsTranslate("usage", "integral(<from: number>, <to: number>, <f: ExecutableObject>)")
|
||||
let usage2 = qsTranslate("usage", "integral(<from: number>, <to: number>, <f: string>, <variable: string>)")
|
||||
let f = this.parseArgumentsForFunction(args, usage1, usage2)
|
||||
if(typeof a !== "number" || typeof b !== "number")
|
||||
if(a == null || b == null)
|
||||
throw EvalError(qsTranslate("usage", "Usage:\n%1\n%2").arg(usage1).arg(usage2))
|
||||
|
||||
// https://en.wikipedia.org/wiki/Simpson%27s_rule
|
||||
|
@ -101,10 +99,10 @@ class ExprParserAPI extends Module {
|
|||
let usage2 = qsTranslate("usage", "derivative(<f: string>, <variable: string>, <x: number>)")
|
||||
let x = args.pop()
|
||||
let f = this.parseArgumentsForFunction(args, usage1, usage2)
|
||||
if(typeof x !== "number")
|
||||
if(x == null)
|
||||
throw EvalError(qsTranslate("usage", "Usage:\n%1\n%2").arg(usage1).arg(usage2))
|
||||
|
||||
let derivative_precision = 1e-8
|
||||
let derivative_precision = x / 10
|
||||
return (f(x + derivative_precision / 2) - f(x - derivative_precision / 2)) / derivative_precision
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -17,164 +17,60 @@
|
|||
*/
|
||||
|
||||
import { Module } from "./common.mjs"
|
||||
import { HelperInterface, NUMBER, STRING } from "./interface.mjs"
|
||||
import { BaseEvent } from "../events.mjs"
|
||||
import { Action, Actions } from "../history/index.mjs"
|
||||
import { HistoryInterface, NUMBER, STRING } from "./interface.mjs"
|
||||
|
||||
|
||||
|
||||
class ClearedEvent extends BaseEvent {
|
||||
constructor() {
|
||||
super("cleared")
|
||||
}
|
||||
}
|
||||
|
||||
class LoadedEvent extends BaseEvent {
|
||||
constructor() {
|
||||
super("loaded")
|
||||
}
|
||||
}
|
||||
|
||||
class AddedEvent extends BaseEvent {
|
||||
constructor(action) {
|
||||
super("added")
|
||||
this.action = action
|
||||
}
|
||||
}
|
||||
|
||||
class UndoneEvent extends BaseEvent {
|
||||
constructor(action) {
|
||||
super("undone")
|
||||
this.undid = action
|
||||
}
|
||||
}
|
||||
|
||||
class RedoneEvent extends BaseEvent {
|
||||
constructor(action) {
|
||||
super("redone")
|
||||
this.redid = action
|
||||
}
|
||||
}
|
||||
|
||||
class HistoryAPI extends Module {
|
||||
static emits = ["cleared", "loaded", "added", "undone", "redone"]
|
||||
|
||||
#helper
|
||||
|
||||
constructor() {
|
||||
super("History", {
|
||||
helper: HelperInterface,
|
||||
historyObj: HistoryInterface,
|
||||
themeTextColor: STRING,
|
||||
imageDepth: NUMBER,
|
||||
fontSize: NUMBER
|
||||
})
|
||||
// History QML object
|
||||
/** @type {Action[]} */
|
||||
this.undoStack = []
|
||||
/** @type {Action[]} */
|
||||
this.redoStack = []
|
||||
|
||||
this.history = null
|
||||
this.themeTextColor = "#FF0000"
|
||||
this.imageDepth = 2
|
||||
this.fontSize = 28
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HelperInterface} historyObj
|
||||
* @param {string} themeTextColor
|
||||
* @param {number} imageDepth
|
||||
* @param {number} fontSize
|
||||
*/
|
||||
initialize({ helper, themeTextColor, imageDepth, fontSize }) {
|
||||
super.initialize({ helper, themeTextColor, imageDepth, fontSize })
|
||||
this.#helper = helper
|
||||
initialize({ historyObj, themeTextColor, imageDepth, fontSize }) {
|
||||
super.initialize({ historyObj, themeTextColor, imageDepth, fontSize })
|
||||
this.history = historyObj
|
||||
this.themeTextColor = themeTextColor
|
||||
this.imageDepth = imageDepth
|
||||
this.fontSize = fontSize
|
||||
}
|
||||
|
||||
/**
|
||||
* Undoes the Action at the top of the undo stack and pushes it to the top of the redo stack.
|
||||
*/
|
||||
undo() {
|
||||
if(!this.initialized) throw new Error("Attempting undo before initialize!")
|
||||
if(this.undoStack.length > 0) {
|
||||
const action = this.undoStack.pop()
|
||||
action.undo()
|
||||
this.redoStack.push(action)
|
||||
this.emit(new UndoneEvent(action))
|
||||
}
|
||||
this.history.undo()
|
||||
}
|
||||
|
||||
/**
|
||||
* Redoes the Action at the top of the redo stack and pushes it to the top of the undo stack.
|
||||
*/
|
||||
redo() {
|
||||
if(!this.initialized) throw new Error("Attempting redo before initialize!")
|
||||
if(this.redoStack.length > 0) {
|
||||
const action = this.redoStack.pop()
|
||||
action.redo()
|
||||
this.undoStack.push(action)
|
||||
this.emit(new RedoneEvent(action))
|
||||
}
|
||||
this.history.redo()
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears both undo and redo stacks completely.
|
||||
*/
|
||||
clear() {
|
||||
if(!this.initialized) throw new Error("Attempting clear before initialize!")
|
||||
this.undoStack = []
|
||||
this.redoStack = []
|
||||
this.emit(new ClearedEvent())
|
||||
this.history.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an instance of HistoryLib.Action to history.
|
||||
* @param action
|
||||
*/
|
||||
addToHistory(action) {
|
||||
if(!this.initialized) throw new Error("Attempting addToHistory before initialize!")
|
||||
if(action instanceof Action) {
|
||||
console.log("Added new entry to history: " + action.getReadableString())
|
||||
this.undoStack.push(action)
|
||||
if(this.#helper.getSetting("reset_redo_stack"))
|
||||
this.redoStack = []
|
||||
this.emit(new AddedEvent(action))
|
||||
}
|
||||
this.history.addToHistory(action)
|
||||
}
|
||||
|
||||
/**
|
||||
* Unserializes both the undo stack and redo stack from serialized content.
|
||||
* @param {[string, any[]][]} undoSt
|
||||
* @param {[string, any[]][]} redoSt
|
||||
*/
|
||||
unserialize(undoSt, redoSt) {
|
||||
unserialize(...data) {
|
||||
if(!this.initialized) throw new Error("Attempting unserialize before initialize!")
|
||||
this.clear()
|
||||
for(const [name, args] of undoSt)
|
||||
this.undoStack.push(
|
||||
new Actions[name](...args)
|
||||
)
|
||||
for(const [name, args] of redoSt)
|
||||
this.redoStack.push(
|
||||
new Actions[name](...args)
|
||||
)
|
||||
this.emit(new LoadedEvent())
|
||||
this.history.unserialize(...data)
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes history into JSON-able content.
|
||||
* @return {[[string, any[]], [string, any[]]]}
|
||||
*/
|
||||
serialize() {
|
||||
if(!this.initialized) throw new Error("Attempting serialize before initialize!")
|
||||
let undoSt = [], redoSt = [];
|
||||
for(const action of this.undoStack)
|
||||
undoSt.push([ action.type(), action.export() ])
|
||||
for(const action of this.redoStack)
|
||||
redoSt.push([ action.type(), action.export() ])
|
||||
return [undoSt, redoSt]
|
||||
return this.history.serialize()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -17,7 +17,6 @@
|
|||
*/
|
||||
|
||||
import Objects from "./objects.mjs"
|
||||
import Settings from "./settings.mjs"
|
||||
import ExprParser from "./expreval.mjs"
|
||||
import Latex from "./latex.mjs"
|
||||
import History from "./history.mjs"
|
||||
|
@ -27,7 +26,6 @@ import Preferences from "./preferences.mjs"
|
|||
|
||||
export default {
|
||||
Objects,
|
||||
Settings,
|
||||
ExprParser,
|
||||
Latex,
|
||||
History,
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/**
|
||||
/*!
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
*
|
||||
* @author Ad5001 <mail@ad5001.eu>
|
||||
* @license GPL-3.0-or-later
|
||||
* @copyright (C) 2021-2025 Ad5001
|
||||
* @copyright (C) 2021-2024 Ad5001
|
||||
* @preserve
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
|
@ -35,8 +35,9 @@ export class Interface {
|
|||
* Throws an error if the implementation does not conform to the interface.
|
||||
* @param {typeof Interface} interface_
|
||||
* @param {object} classToCheck
|
||||
* @return {boolean}
|
||||
*/
|
||||
static checkImplementation(interface_, classToCheck) {
|
||||
static check_implementation(interface_, classToCheck) {
|
||||
const properties = new interface_()
|
||||
const interfaceName = interface_.name
|
||||
const toCheckName = classToCheck.constructor.name
|
||||
|
@ -51,7 +52,7 @@ export class Interface {
|
|||
else if((typeof value) === "object")
|
||||
// Test type of object.
|
||||
if(value instanceof Interface)
|
||||
Interface.checkImplementation(value, classToCheck[property])
|
||||
Interface.check_implementation(value, classToCheck[property])
|
||||
else if(value.prototype && !(classToCheck[property] instanceof value))
|
||||
throw new Error(`Property '${property}' of ${interfaceName} implementation ${toCheckName} is not '${value.constructor.name}'.`)
|
||||
}
|
||||
|
@ -59,13 +60,32 @@ export class Interface {
|
|||
}
|
||||
|
||||
|
||||
export class CanvasInterface extends Interface {
|
||||
export class SettingsInterface extends Interface {
|
||||
width = NUMBER
|
||||
height = NUMBER
|
||||
xmin = NUMBER
|
||||
ymax = NUMBER
|
||||
xzoom = NUMBER
|
||||
yzoom = NUMBER
|
||||
xaxisstep = STRING
|
||||
yaxisstep = STRING
|
||||
xlabel = STRING
|
||||
ylabel = STRING
|
||||
linewidth = NUMBER
|
||||
textsize = NUMBER
|
||||
logscalex = BOOLEAN
|
||||
showxgrad = BOOLEAN
|
||||
showygrad = BOOLEAN
|
||||
}
|
||||
|
||||
export class CanvasInterface extends SettingsInterface {
|
||||
imageLoaders = OBJECT
|
||||
/** @type {function(string): CanvasRenderingContext2D} */
|
||||
getContext = FUNCTION
|
||||
/** @type {function(rect)} */
|
||||
markDirty = FUNCTION
|
||||
/** @type {function(string): Promise} */
|
||||
loadImageAsync = FUNCTION
|
||||
/** @type {function(string)} */
|
||||
loadImage = FUNCTION
|
||||
/** @type {function(string)} */
|
||||
isImageLoading = FUNCTION
|
||||
/** @type {function(string)} */
|
||||
|
@ -77,28 +97,30 @@ export class CanvasInterface extends Interface {
|
|||
export class RootInterface extends Interface {
|
||||
width = NUMBER
|
||||
height = NUMBER
|
||||
updateObjectsLists = FUNCTION
|
||||
}
|
||||
|
||||
export class DialogInterface extends Interface {
|
||||
show = FUNCTION
|
||||
}
|
||||
|
||||
export class HistoryInterface extends Interface {
|
||||
undo = FUNCTION
|
||||
redo = FUNCTION
|
||||
clear = FUNCTION
|
||||
addToHistory = FUNCTION
|
||||
unserialize = FUNCTION
|
||||
serialize = FUNCTION
|
||||
}
|
||||
|
||||
export class LatexInterface extends Interface {
|
||||
supportsAsyncRender = BOOLEAN
|
||||
/**
|
||||
* @param {string} markup - LaTeX markup to render
|
||||
* @param {number} fontSize - Font size (in pt) to render
|
||||
* @param {string} color - Color of the text to render
|
||||
* @returns {string} - Comma separated data of the image (source, width, height)
|
||||
*/
|
||||
renderSync = FUNCTION
|
||||
/**
|
||||
* @param {string} markup - LaTeX markup to render
|
||||
* @param {number} fontSize - Font size (in pt) to render
|
||||
* @param {string} color - Color of the text to render
|
||||
* @returns {Promise<string>} - Comma separated data of the image (source, width, height)
|
||||
*/
|
||||
renderAsync = FUNCTION
|
||||
render = FUNCTION
|
||||
/**
|
||||
* @param {string} markup - LaTeX markup to render
|
||||
* @param {number} fontSize - Font size (in pt) to render
|
||||
|
@ -117,13 +139,37 @@ export class HelperInterface extends Interface {
|
|||
/**
|
||||
* Gets a setting from the config
|
||||
* @param {string} settingName - Setting (and its dot-separated namespace) to get (e.g. "default_graph.xmin")
|
||||
* @returns {string|number|boolean} Value of the setting
|
||||
* @returns {boolean} Value of the setting
|
||||
*/
|
||||
getSettingBool = FUNCTION
|
||||
/**
|
||||
* Gets a setting from the config
|
||||
* @param {string} settingName - Setting (and its dot-separated namespace) to get (e.g. "default_graph.xmin")
|
||||
* @returns {number} Value of the setting
|
||||
*/
|
||||
getSettingInt = FUNCTION
|
||||
/**
|
||||
* Gets a setting from the config
|
||||
* @param {string} settingName - Setting (and its dot-separated namespace) to get (e.g. "default_graph.xmin")
|
||||
* @returns {string} Value of the setting
|
||||
*/
|
||||
getSetting = FUNCTION
|
||||
/**
|
||||
* Sets a setting in the config
|
||||
* @param {string} settingName - Setting (and its dot-separated namespace) to set (e.g. "default_graph.xmin")
|
||||
* @param {string|number|boolean} value
|
||||
* @param {boolean} value
|
||||
*/
|
||||
setSettingBool = FUNCTION
|
||||
/**
|
||||
* Sets a setting in the config
|
||||
* @param {string} settingName - Setting (and its dot-separated namespace) to set (e.g. "default_graph.xmin")
|
||||
* @param {number} value
|
||||
*/
|
||||
setSettingInt = FUNCTION
|
||||
/**
|
||||
* Sets a setting in the config
|
||||
* @param {string} settingName - Setting (and its dot-separated namespace) to set (e.g. "default_graph.xmin")
|
||||
* @param {string} value
|
||||
*/
|
||||
setSetting = FUNCTION
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -19,69 +19,36 @@
|
|||
import { Module } from "./common.mjs"
|
||||
import Objects from "./objects.mjs"
|
||||
import History from "./history.mjs"
|
||||
import Settings from "./settings.mjs"
|
||||
import { DialogInterface, RootInterface } from "./interface.mjs"
|
||||
import { BaseEvent } from "../events.mjs"
|
||||
import Canvas from "./canvas.mjs"
|
||||
import { DialogInterface, RootInterface, SettingsInterface } from "./interface.mjs"
|
||||
|
||||
|
||||
class LoadedEvent extends BaseEvent {
|
||||
constructor() {
|
||||
super("loaded")
|
||||
}
|
||||
}
|
||||
|
||||
class SavedEvent extends BaseEvent {
|
||||
constructor() {
|
||||
super("saved")
|
||||
}
|
||||
}
|
||||
|
||||
class ModifiedEvent extends BaseEvent {
|
||||
constructor() {
|
||||
super("modified")
|
||||
}
|
||||
}
|
||||
|
||||
class IOAPI extends Module {
|
||||
static emits = ["loaded", "saved", "modified"]
|
||||
|
||||
/** @type {RootInterface} */
|
||||
#rootElement
|
||||
/** @type {{show: function(string)}} */
|
||||
#alert
|
||||
#saved = true
|
||||
|
||||
constructor() {
|
||||
super("IO", {
|
||||
alert: DialogInterface,
|
||||
root: RootInterface
|
||||
root: RootInterface,
|
||||
settings: SettingsInterface
|
||||
})
|
||||
|
||||
// Settings.on("changed", this.__emitModified.bind(this))
|
||||
History.on("added undone redone", this.__emitModified.bind(this))
|
||||
}
|
||||
|
||||
__emitModified() {
|
||||
this.#saved = false
|
||||
this.emit(new ModifiedEvent())
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* True if no changes have been made since last save, false otherwise.
|
||||
* @return {boolean}
|
||||
* Path of the currently opened file. Empty if no file is opened.
|
||||
* @type {string}
|
||||
*/
|
||||
get saved() { return this.#saved }
|
||||
this.saveFileName = ""
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes module with QML elements.
|
||||
* @param {RootInterface} root
|
||||
* @param {SettingsInterface} settings
|
||||
* @param {{show: function(string)}} alert
|
||||
*/
|
||||
initialize({ root, alert }) {
|
||||
super.initialize({ root, alert })
|
||||
this.#rootElement = root
|
||||
this.#alert = alert
|
||||
initialize({ root, settings, alert }) {
|
||||
super.initialize({ root, settings, alert })
|
||||
this.rootElement = root
|
||||
this.settings = settings
|
||||
this.alert = alert
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -93,7 +60,7 @@ class IOAPI extends Module {
|
|||
// Add extension if necessary
|
||||
if(["lpf"].indexOf(filename.split(".")[filename.split(".").length - 1]) === -1)
|
||||
filename += ".lpf"
|
||||
Settings.set("saveFilename", filename, false)
|
||||
this.saveFilename = filename
|
||||
let objs = {}
|
||||
for(let objType in Objects.currentObjects) {
|
||||
objs[objType] = []
|
||||
|
@ -102,29 +69,28 @@ class IOAPI extends Module {
|
|||
}
|
||||
}
|
||||
let settings = {
|
||||
"xzoom": Settings.xzoom,
|
||||
"yzoom": Settings.yzoom,
|
||||
"xmin": Settings.xmin,
|
||||
"ymax": Settings.ymax,
|
||||
"xaxisstep": Settings.xaxisstep,
|
||||
"yaxisstep": Settings.yaxisstep,
|
||||
"xaxislabel": Settings.xlabel,
|
||||
"yaxislabel": Settings.ylabel,
|
||||
"logscalex": Settings.logscalex,
|
||||
"linewidth": Settings.linewidth,
|
||||
"showxgrad": Settings.showxgrad,
|
||||
"showygrad": Settings.showygrad,
|
||||
"textsize": Settings.textsize,
|
||||
"xzoom": this.settings.xzoom,
|
||||
"yzoom": this.settings.yzoom,
|
||||
"xmin": this.settings.xmin,
|
||||
"ymax": this.settings.ymax,
|
||||
"xaxisstep": this.settings.xaxisstep,
|
||||
"yaxisstep": this.settings.yaxisstep,
|
||||
"xaxislabel": this.settings.xlabel,
|
||||
"yaxislabel": this.settings.ylabel,
|
||||
"logscalex": this.settings.logscalex,
|
||||
"linewidth": this.settings.linewidth,
|
||||
"showxgrad": this.settings.showxgrad,
|
||||
"showygrad": this.settings.showygrad,
|
||||
"textsize": this.settings.textsize,
|
||||
"history": History.serialize(),
|
||||
"width": this.#rootElement.width,
|
||||
"height": this.#rootElement.height,
|
||||
"width": this.rootElement.width,
|
||||
"height": this.rootElement.height,
|
||||
"objects": objs,
|
||||
"type": "logplotv1"
|
||||
}
|
||||
Helper.write(filename, JSON.stringify(settings))
|
||||
this.#alert.show(qsTranslate("io", "Saved plot to '%1'.").arg(filename.split("/").pop()))
|
||||
this.#saved = true
|
||||
this.emit(new SavedEvent())
|
||||
this.alert.show(qsTranslate("io", "Saved plot to '%1'.").arg(filename.split("/").pop()))
|
||||
History.history.saved = true
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -135,32 +101,32 @@ class IOAPI extends Module {
|
|||
if(!this.initialized) throw new Error("Attempting loadDiagram before initialize!")
|
||||
if(!History.initialized) throw new Error("Attempting loadDiagram before history is initialized!")
|
||||
let basename = filename.split("/").pop()
|
||||
this.#alert.show(qsTranslate("io", "Loading file '%1'.").arg(basename))
|
||||
this.alert.show(qsTranslate("io", "Loading file '%1'.").arg(basename))
|
||||
let data = JSON.parse(Helper.load(filename))
|
||||
let error = ""
|
||||
if(data.hasOwnProperty("type") && data["type"] === "logplotv1") {
|
||||
History.clear()
|
||||
// Importing settings
|
||||
Settings.set("saveFilename", filename, false)
|
||||
Settings.set("xzoom", parseFloat(data["xzoom"]) || 100, false)
|
||||
Settings.set("yzoom", parseFloat(data["yzoom"]) || 10, false)
|
||||
Settings.set("xmin", parseFloat(data["xmin"]) || 5 / 10, false)
|
||||
Settings.set("ymax", parseFloat(data["ymax"]) || 24, false)
|
||||
Settings.set("xaxisstep", data["xaxisstep"] || "4", false)
|
||||
Settings.set("yaxisstep", data["yaxisstep"] || "4", false)
|
||||
Settings.set("xlabel", data["xaxislabel"] || "", false)
|
||||
Settings.set("ylabel", data["yaxislabel"] || "", false)
|
||||
Settings.set("logscalex", data["logscalex"] === true, false)
|
||||
this.settings.saveFilename = filename
|
||||
this.settings.xzoom = parseFloat(data["xzoom"]) || 100
|
||||
this.settings.yzoom = parseFloat(data["yzoom"]) || 10
|
||||
this.settings.xmin = parseFloat(data["xmin"]) || 5 / 10
|
||||
this.settings.ymax = parseFloat(data["ymax"]) || 24
|
||||
this.settings.xaxisstep = data["xaxisstep"] || "4"
|
||||
this.settings.yaxisstep = data["yaxisstep"] || "4"
|
||||
this.settings.xlabel = data["xaxislabel"] || ""
|
||||
this.settings.ylabel = data["yaxislabel"] || ""
|
||||
this.settings.logscalex = data["logscalex"] === true
|
||||
if("showxgrad" in data)
|
||||
Settings.set("showxgrad", data["showxgrad"], false)
|
||||
this.settings.showxgrad = data["showxgrad"]
|
||||
if("showygrad" in data)
|
||||
Settings.set("showygrad", data["showygrad"], false)
|
||||
this.settings.textsize = data["showygrad"]
|
||||
if("linewidth" in data)
|
||||
Settings.set("linewidth", data["linewidth"], false)
|
||||
this.settings.linewidth = data["linewidth"]
|
||||
if("textsize" in data)
|
||||
Settings.set("textsize", data["textsize"], false)
|
||||
this.#rootElement.height = parseFloat(data["height"]) || 500
|
||||
this.#rootElement.width = parseFloat(data["width"]) || 1000
|
||||
this.settings.textsize = data["textsize"]
|
||||
this.rootElement.height = parseFloat(data["height"]) || 500
|
||||
this.rootElement.width = parseFloat(data["width"]) || 1000
|
||||
|
||||
// Importing objects
|
||||
Objects.currentObjects = {}
|
||||
|
@ -191,18 +157,20 @@ class IOAPI extends Module {
|
|||
if("history" in data)
|
||||
History.unserialize(...data["history"])
|
||||
|
||||
// Refreshing sidebar
|
||||
this.rootElement.updateObjectsLists()
|
||||
} else {
|
||||
error = qsTranslate("io", "Invalid file provided.")
|
||||
}
|
||||
if(error !== "") {
|
||||
console.log(error)
|
||||
this.#alert.show(qsTranslate("io", "Could not load file: ") + error)
|
||||
this.alert.show(qsTranslate("io", "Could not load file: ") + error)
|
||||
// TODO: Error handling
|
||||
return
|
||||
}
|
||||
this.#alert.show(qsTranslate("io", "Loaded file '%1'.").arg(basename))
|
||||
this.#saved = true
|
||||
this.emit(new LoadedEvent())
|
||||
Canvas.redraw()
|
||||
this.alert.show(qsTranslate("io", "Loaded file '%1'.").arg(basename))
|
||||
History.history.saved = true
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -17,12 +17,11 @@
|
|||
*/
|
||||
|
||||
import { Module } from "./common.mjs"
|
||||
import { BaseEvent } from "../events.mjs"
|
||||
import * as Instruction from "../lib/expr-eval/instruction.mjs"
|
||||
import { escapeValue } from "../lib/expr-eval/expression.mjs"
|
||||
import { HelperInterface, LatexInterface } from "./interface.mjs"
|
||||
|
||||
const unicodechars = ["pi", "∞",
|
||||
const unicodechars = [
|
||||
"α", "β", "γ", "δ", "ε", "ζ", "η",
|
||||
"π", "θ", "κ", "λ", "μ", "ξ", "ρ",
|
||||
"ς", "σ", "τ", "φ", "χ", "ψ", "ω",
|
||||
|
@ -31,9 +30,9 @@ const unicodechars = ["pi", "∞",
|
|||
"ₕ", "ₖ", "ₗ", "ₘ", "ₙ", "ₚ", "ₛ",
|
||||
"ₜ", "¹", "²", "³", "⁴", "⁵", "⁶",
|
||||
"⁷", "⁸", "⁹", "⁰", "₁", "₂", "₃",
|
||||
"₄", "₅", "₆", "₇", "₈", "₉", "₀"
|
||||
]
|
||||
const equivalchars = ["\\pi", "\\infty",
|
||||
"₄", "₅", "₆", "₇", "₈", "₉", "₀",
|
||||
"pi", "∞"]
|
||||
const equivalchars = [
|
||||
"\\alpha", "\\beta", "\\gamma", "\\delta", "\\epsilon", "\\zeta", "\\eta",
|
||||
"\\pi", "\\theta", "\\kappa", "\\lambda", "\\mu", "\\xi", "\\rho",
|
||||
"\\sigma", "\\sigma", "\\tau", "\\phi", "\\chi", "\\psi", "\\omega",
|
||||
|
@ -43,29 +42,7 @@ const equivalchars = ["\\pi", "\\infty",
|
|||
"{}_{t}", "{}^{1}", "{}^{2}", "{}^{3}", "{}^{4}", "{}^{5}", "{}^{6}",
|
||||
"{}^{7}", "{}^{8}", "{}^{9}", "{}^{0}", "{}_{1}", "{}_{2}", "{}_{3}",
|
||||
"{}_{4}", "{}_{5}", "{}_{6}", "{}_{7}", "{}_{8}", "{}_{9}", "{}_{0}",
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
class AsyncRenderStartedEvent extends BaseEvent {
|
||||
constructor(markup, fontSize, color) {
|
||||
super("async-render-started")
|
||||
this.markup = markup
|
||||
this.fontSize = fontSize
|
||||
this.color = color
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AsyncRenderFinishedEvent extends BaseEvent {
|
||||
constructor(markup, fontSize, color) {
|
||||
super("async-render-finished")
|
||||
this.markup = markup
|
||||
this.fontSize = fontSize
|
||||
this.color = color
|
||||
}
|
||||
}
|
||||
"\\pi", "\\infty"]
|
||||
|
||||
/**
|
||||
* Class containing the result of a LaTeX render.
|
||||
|
@ -83,11 +60,6 @@ class LatexRenderResult {
|
|||
}
|
||||
|
||||
class LatexAPI extends Module {
|
||||
static emits = ["async-render-started", "async-render-finished"]
|
||||
|
||||
/** @type {LatexInterface} */
|
||||
#latex = null
|
||||
|
||||
constructor() {
|
||||
super("Latex", {
|
||||
latex: LatexInterface,
|
||||
|
@ -97,7 +69,6 @@ class LatexAPI extends Module {
|
|||
* true if latex has been enabled by the user, false otherwise.
|
||||
*/
|
||||
this.enabled = false
|
||||
this.promises = new Set()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -106,8 +77,9 @@ class LatexAPI extends Module {
|
|||
*/
|
||||
initialize({ latex, helper }) {
|
||||
super.initialize({ latex, helper })
|
||||
this.#latex = latex
|
||||
this.enabled = helper.getSetting("enable_latex")
|
||||
this.latex = latex
|
||||
this.helper = helper
|
||||
this.enabled = helper.getSettingBool("enable_latex")
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -121,7 +93,7 @@ class LatexAPI extends Module {
|
|||
*/
|
||||
findPrerendered(markup, fontSize, color) {
|
||||
if(!this.initialized) throw new Error("Attempting findPrerendered before initialize!")
|
||||
const data = this.#latex.findPrerendered(markup, fontSize, color)
|
||||
const data = this.latex.findPrerendered(markup, fontSize, color)
|
||||
let ret = null
|
||||
if(data !== "")
|
||||
ret = new LatexRenderResult(...data.split(","))
|
||||
|
@ -138,19 +110,7 @@ class LatexAPI extends Module {
|
|||
*/
|
||||
async requestAsyncRender(markup, fontSize, color) {
|
||||
if(!this.initialized) throw new Error("Attempting requestAsyncRender before initialize!")
|
||||
let render
|
||||
if(this.#latex.supportsAsyncRender) {
|
||||
this.emit(new AsyncRenderStartedEvent(markup, fontSize, color))
|
||||
// Storing promise so that it does not get dereferenced.
|
||||
const promise = this.#latex.renderAsync(markup, fontSize, color)
|
||||
this.promises.add(promise)
|
||||
render = await promise
|
||||
this.promises.delete(promise)
|
||||
this.emit(new AsyncRenderFinishedEvent(markup, fontSize, color))
|
||||
} else {
|
||||
render = this.#latex.renderSync(markup, fontSize, color)
|
||||
}
|
||||
const args = render.split(",")
|
||||
let args = this.latex.render(markup, fontSize, color).split(",")
|
||||
return new LatexRenderResult(...args)
|
||||
}
|
||||
|
||||
|
@ -175,10 +135,9 @@ class LatexAPI extends Module {
|
|||
*/
|
||||
parif(elem, contents) {
|
||||
elem = elem.toString()
|
||||
const contains = contents.some(x => elem.indexOf(x) > 0)
|
||||
if(contains && (elem[0] !== "(" || elem.at(-1) !== ")"))
|
||||
if(elem[0] !== "(" && elem[elem.length - 1] !== ")" && contents.some(x => elem.indexOf(x) > 0))
|
||||
return this.par(elem)
|
||||
if(!contains && elem[0] === "(" && elem.at(-1) === ")")
|
||||
if(elem[0] === "(" && elem[elem.length - 1] === ")")
|
||||
return elem.removeEnclosure()
|
||||
return elem
|
||||
}
|
||||
|
@ -196,21 +155,20 @@ class LatexAPI extends Module {
|
|||
if(args.length === 3)
|
||||
return `\\frac{d${args[0].removeEnclosure().replaceAll(args[1].removeEnclosure(), "x")}}{dx}`
|
||||
else
|
||||
return `\\frac{d${args[0]}}{dx}(${args[1]})`
|
||||
return `\\frac{d${args[0]}}{dx}(x)`
|
||||
case "integral":
|
||||
if(args.length === 4)
|
||||
return `\\int\\limits_{${args[0]}}^{${args[1]}}${args[2].removeEnclosure()} d${args[3].removeEnclosure()}`
|
||||
else
|
||||
return `\\int\\limits_{${args[0]}}^{${args[1]}}${args[2]}(t) dt`
|
||||
case "sqrt":
|
||||
const arg = this.parif(args.join(", "), [])
|
||||
return `\\sqrt{${arg}}`
|
||||
return `\\sqrt\\left(${args.join(", ")}\\right)`
|
||||
case "abs":
|
||||
return `\\left|${args.join(", ")}\\right|`
|
||||
case "floor":
|
||||
return `\\left\\lfloor{${args.join(", ")}}\\right\\rfloor`
|
||||
return `\\left\\lfloor${args.join(", ")}\\right\\rfloor`
|
||||
case "ceil":
|
||||
return `\\left\\lceil{${args.join(", ")}}\\right\\rceil`
|
||||
return `\\left\\lceil${args.join(", ")}\\right\\rceil`
|
||||
default:
|
||||
return `\\mathrm{${f}}\\left(${args.join(", ")}\\right)`
|
||||
}
|
||||
|
@ -224,17 +182,16 @@ class LatexAPI extends Module {
|
|||
* @returns {string}
|
||||
*/
|
||||
variable(vari, wrapIn$ = false) {
|
||||
if(wrapIn$) {
|
||||
if(wrapIn$)
|
||||
for(let i = 0; i < unicodechars.length; i++) {
|
||||
if(vari.includes(unicodechars[i]))
|
||||
vari = vari.replaceAll(unicodechars[i], "$" + equivalchars[i] + "$")
|
||||
}
|
||||
} else {
|
||||
else
|
||||
for(let i = 0; i < unicodechars.length; i++) {
|
||||
if(vari.includes(unicodechars[i]))
|
||||
vari = vari.replaceAll(unicodechars[i], equivalchars[i])
|
||||
}
|
||||
}
|
||||
return vari
|
||||
}
|
||||
|
||||
|
@ -302,7 +259,7 @@ class LatexAPI extends Module {
|
|||
throw new EvalError("Unknown operator " + item.value + ".")
|
||||
}
|
||||
break
|
||||
case Instruction.IOP3: // Ternary operator
|
||||
case Instruction.IOP3: // Thirdiary operator
|
||||
n3 = nstack.pop()
|
||||
n2 = nstack.pop()
|
||||
n1 = nstack.pop()
|
||||
|
@ -329,7 +286,7 @@ class LatexAPI extends Module {
|
|||
nstack.push(this.parif(n1, ["+", "-", "*", "/", "^"]) + "!")
|
||||
break
|
||||
default:
|
||||
nstack.push(this.functionToLatex(f, [this.parif(n1, ["+", "-", "*", "/", "^"])]))
|
||||
nstack.push(f + this.parif(n1, ["+", "-", "*", "/", "^"]))
|
||||
break
|
||||
}
|
||||
break
|
||||
|
@ -364,6 +321,9 @@ class LatexAPI extends Module {
|
|||
throw new EvalError("invalid Expression")
|
||||
}
|
||||
}
|
||||
if(nstack.length > 1) {
|
||||
nstack = [nstack.join(";")]
|
||||
}
|
||||
return String(nstack[0])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -17,17 +17,13 @@
|
|||
*/
|
||||
|
||||
import { Module } from "./common.mjs"
|
||||
import { textsub } from "../utils/index.mjs"
|
||||
import { textsub } from "../utils.mjs"
|
||||
|
||||
class ObjectsAPI extends Module {
|
||||
|
||||
constructor() {
|
||||
super("Objects")
|
||||
|
||||
/**
|
||||
* List of object constructors.
|
||||
* @type {Object.<string,typeof DrawableObject>}
|
||||
*/
|
||||
this.types = {}
|
||||
/**
|
||||
* List of objects for each type of object.
|
||||
|
@ -69,7 +65,7 @@ class ObjectsAPI extends Module {
|
|||
* @param {string} newName - Name to rename the object to.
|
||||
*/
|
||||
renameObject(oldName, newName) {
|
||||
const obj = this.currentObjectsByName[oldName]
|
||||
let obj = this.currentObjectsByName[oldName]
|
||||
delete this.currentObjectsByName[oldName]
|
||||
this.currentObjectsByName[newName] = obj
|
||||
obj.name = newName
|
||||
|
@ -80,7 +76,7 @@ class ObjectsAPI extends Module {
|
|||
* @param {string} objName - Current name of the object.
|
||||
*/
|
||||
deleteObject(objName) {
|
||||
const obj = this.currentObjectsByName[objName]
|
||||
let obj = this.currentObjectsByName[objName]
|
||||
if(obj !== undefined) {
|
||||
this.currentObjects[obj.type].splice(this.currentObjects[obj.type].indexOf(obj), 1)
|
||||
obj.delete()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -20,9 +20,6 @@ import General from "../preferences/general.mjs"
|
|||
import Editor from "../preferences/expression.mjs"
|
||||
import DefaultGraph from "../preferences/default.mjs"
|
||||
|
||||
/**
|
||||
* Module for application wide settings.
|
||||
*/
|
||||
class PreferencesAPI extends Module {
|
||||
constructor() {
|
||||
super("Preferences")
|
||||
|
|
|
@ -1,186 +0,0 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Module } from "./common.mjs"
|
||||
import { BaseEvent } from "../events.mjs"
|
||||
import { HelperInterface } from "./interface.mjs"
|
||||
|
||||
|
||||
/**
|
||||
* Base event for when a setting was changed.
|
||||
*/
|
||||
class ChangedEvent extends BaseEvent {
|
||||
/**
|
||||
*
|
||||
* @param {string} property - Name of the property that was chagned
|
||||
* @param {string|number|boolean} oldValue - Old value of the property
|
||||
* @param {string|number|boolean} newValue - Current (new) value of the property
|
||||
* @param {boolean} byUser - True if the user is at the source of the change in the setting.
|
||||
*/
|
||||
constructor(property, oldValue, newValue, byUser) {
|
||||
super("changed")
|
||||
|
||||
this.property = property
|
||||
this.oldValue = oldValue
|
||||
this.newValue = newValue
|
||||
this.byUser = byUser
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Module for graph settings.
|
||||
*/
|
||||
class SettingsAPI extends Module {
|
||||
static emits = ["changed"]
|
||||
|
||||
#nonConfigurable = ["saveFilename"]
|
||||
|
||||
/** @type {Map<string, string|number|boolean>} */
|
||||
#properties = new Map([
|
||||
["saveFilename", ""],
|
||||
["xzoom", 100],
|
||||
["yzoom", 10],
|
||||
["xmin", .5],
|
||||
["ymax", 25],
|
||||
["xaxisstep", "4"],
|
||||
["yaxisstep", "4"],
|
||||
["xlabel", ""],
|
||||
["ylabel", ""],
|
||||
["linewidth", 1],
|
||||
["textsize", 18],
|
||||
["logscalex", true],
|
||||
["showxgrad", true],
|
||||
["showygrad", true]
|
||||
])
|
||||
|
||||
constructor() {
|
||||
super("Settings", {
|
||||
helper: HelperInterface
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {HelperInterface} helper
|
||||
*/
|
||||
initialize({ helper }) {
|
||||
super.initialize({ helper })
|
||||
// Initialize default values.
|
||||
for(const key of this.#properties.keys())
|
||||
if(!this.#nonConfigurable.includes(key))
|
||||
this.set(key, helper.getSetting("default_graph."+key), false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a setting to a given value
|
||||
*
|
||||
* @param {string} property
|
||||
* @param {string|number|boolean} value
|
||||
* @param {boolean} byUser - Set to true if the user is at the origin of this change.
|
||||
*/
|
||||
set(property, value, byUser) {
|
||||
if(!this.#properties.has(property))
|
||||
throw new Error(`Property ${property} is not a setting.`)
|
||||
const oldValue = this.#properties.get(property)
|
||||
const propType = typeof oldValue
|
||||
if(byUser)
|
||||
console.debug("Setting", property, "from", oldValue, "to", value, `(${typeof value}, ${byUser})`)
|
||||
if(propType !== typeof value)
|
||||
throw new Error(`Value of ${property} must be a ${propType} (${typeof value} provided).`)
|
||||
this.#properties.set(property, value)
|
||||
const evt = new ChangedEvent(property, oldValue, value, byUser === true)
|
||||
this.emit(evt)
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of the currently opened file.
|
||||
* @returns {string}
|
||||
*/
|
||||
get saveFilename() { return this.#properties.get("saveFilename") }
|
||||
|
||||
/**
|
||||
* Zoom on the x axis of the diagram.
|
||||
* @returns {number}
|
||||
*/
|
||||
get xzoom() { return this.#properties.get("xzoom") }
|
||||
/**
|
||||
* Zoom on the y axis of the diagram.
|
||||
* @returns {number}
|
||||
*/
|
||||
get yzoom() { return this.#properties.get("yzoom") }
|
||||
/**
|
||||
* Minimum x of the diagram.
|
||||
* @returns {number}
|
||||
*/
|
||||
get xmin() { return this.#properties.get("xmin") }
|
||||
/**
|
||||
* Maximum y of the diagram.
|
||||
* @returns {number}
|
||||
*/
|
||||
get ymax() { return this.#properties.get("ymax") }
|
||||
/**
|
||||
* Step of the x axis graduation (expression).
|
||||
* @note Only available in non-logarithmic mode.
|
||||
* @returns {string}
|
||||
*/
|
||||
get xaxisstep() { return this.#properties.get("xaxisstep") }
|
||||
/**
|
||||
* Step of the y axis graduation (expression).
|
||||
* @returns {string}
|
||||
*/
|
||||
get yaxisstep() { return this.#properties.get("yaxisstep") }
|
||||
/**
|
||||
* Label used on the x axis.
|
||||
* @returns {string}
|
||||
*/
|
||||
get xlabel() { return this.#properties.get("xlabel") }
|
||||
/**
|
||||
* Label used on the y axis.
|
||||
* @returns {string}
|
||||
*/
|
||||
get ylabel() { return this.#properties.get("ylabel") }
|
||||
/**
|
||||
* Width of lines that will be drawn into the canvas.
|
||||
* @returns {number}
|
||||
*/
|
||||
get linewidth() { return this.#properties.get("linewidth") }
|
||||
/**
|
||||
* Font size of the text that will be drawn into the canvas.
|
||||
* @returns {number}
|
||||
*/
|
||||
get textsize() { return this.#properties.get("textsize") }
|
||||
/**
|
||||
* true if the canvas should be in logarithmic mode, false otherwise.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get logscalex() { return this.#properties.get("logscalex") }
|
||||
/**
|
||||
* true if the x graduation should be shown, false otherwise.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get showxgrad() { return this.#properties.get("showxgrad") }
|
||||
/**
|
||||
* true if the y graduation should be shown, false otherwise.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get showygrad() { return this.#properties.get("showygrad") }
|
||||
}
|
||||
|
||||
Modules.Settings = Modules.Settings || new SettingsAPI()
|
||||
export default Modules.Settings
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -63,7 +63,7 @@ export default class BodePhase extends ExecutableObject {
|
|||
// Create new point
|
||||
om_0 = Objects.createNewRegisteredObject("Point", [Objects.getNewName("ω"), this.color, "name"])
|
||||
om_0.labelPosition = this.phase.execute() >= 0 ? "above" : "below"
|
||||
History.addToHistory(new CreateNewObject(om_0.name, "Point", om_0.export()))
|
||||
History.history.addToHistory(new CreateNewObject(om_0.name, "Point", om_0.export()))
|
||||
labelPosition = "below"
|
||||
}
|
||||
om_0.requiredBy.push(this)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -16,9 +16,9 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { getRandomColor } from "../utils.mjs"
|
||||
import Objects from "../module/objects.mjs"
|
||||
import Latex from "../module/latex.mjs"
|
||||
import { getRandomColor } from "../utils/index.mjs"
|
||||
import { ensureTypeSafety, serializesByPropertyType } from "../parameters.mjs"
|
||||
|
||||
// This file contains the default data to be imported from all other objects
|
||||
|
@ -224,7 +224,7 @@ export class DrawableObject {
|
|||
currentObjectsByName[objName].requiredBy.push(this)
|
||||
}
|
||||
}
|
||||
if(this[property].canBeCached && this[property].requiredObjects().length > 0)
|
||||
if(this[property].cached && this[property].requiredObjects().length > 0)
|
||||
// Recalculate
|
||||
this[property].recache()
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2025 Ad5001
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
|