Compare commits
261 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a2f8a924e0 | ||
fbef5dc28a | |||
8245264874 | |||
310afa5672 | |||
![]() |
5fa118233c | ||
2c8011056d | |||
54f82eab92 | |||
6101c0c645 | |||
b673038b15 | |||
3b244fad2c | |||
![]() |
004b3f5612 | ||
6025742e86 | |||
fdcc8105ba | |||
da8aa59b0a | |||
f5b489ef44 | |||
8429ff3739 | |||
440575325e | |||
45fef876ec | |||
43e41a5da4 | |||
687b14429a | |||
![]() |
49e94317d4 | ||
727dda2623 | |||
27c9fe0473 | |||
e6de739d0c | |||
f52ee65c56 | |||
8da10497d2 | |||
5d0542ffcc | |||
90f4691c54 | |||
c39498c60f | |||
2594fd6844 | |||
a01b7a17ef | |||
67799e9908 | |||
d53f50193a | |||
14e8cef6af | |||
b989a685e9 | |||
e35f6cebec | |||
6251835aa0 | |||
14c092b9fa | |||
811262b1fb | |||
3c0d99d9c0 | |||
2899ac6cde | |||
a182c703f4 | |||
ef465b34e7 | |||
8fab9d8e52 | |||
34caf20593 | |||
a85a4721e3 | |||
aeaaba759f | |||
ccddb068a6 | |||
37ac400f23 | |||
5313428250 | |||
cf73b35a9a | |||
f734e40ad9 | |||
b33e1329db | |||
2995b2271a | |||
a26dbc8a00 | |||
89e78913de | |||
c03afdf4ee | |||
3a81441d0b | |||
edf4518494 | |||
345458f453 | |||
974baa6cc2 | |||
4c1b705240 | |||
885d1f5dc3 | |||
0abb22130f | |||
42d5add810 | |||
e2d259f866 | |||
8a878b4cc1 | |||
07e58a3a55 | |||
c592b92212 | |||
7935d0134d | |||
5745587c72 | |||
84adc787e5 | |||
f3307b47d9 | |||
9017f84c06 | |||
00ab895b21 | |||
82e6d2ffe3 | |||
b91dbfb311 | |||
448d94fee3 | |||
2dc9234b22 | |||
54363b25bc | |||
52f859349a | |||
d1ac70a946 | |||
f4920aadb6 | |||
af2950c3d2 | |||
![]() |
bd346240bd | ||
9663c33563 | |||
934dd3ea1b | |||
b02ed87a29 | |||
40d86c8f82 | |||
6b3cce4252 | |||
8c273f4220 | |||
a60ac79d83 | |||
23c3b771c2 | |||
041d4f424e | |||
1fc19f6ba3 | |||
![]() |
7ef55e48e8 | ||
07e556da56 | |||
cd6f258720 | |||
1c7e9d627d | |||
e2841c0129 | |||
ca5c7492dc | |||
34cb856dd4 | |||
e9d204daab | |||
a2443d7915 | |||
56a0817960 | |||
c74c2fb747 | |||
c2eae30bd6 | |||
80cea6d280 | |||
f8ce98d4ad | |||
c806f09b10 | |||
c32d70e9ed | |||
8cefc56ac7 | |||
850d076268 | |||
95b47effdf | |||
49aa23de92 | |||
4a1b333198 | |||
b5bdbb6294 | |||
309b0fafb0 | |||
fbb85083c1 | |||
f9af0c34dd | |||
![]() |
17b6e40d60 | ||
937cb07d0b | |||
6a1f01ba1f | |||
4c1403c983 | |||
8c8964e75e | |||
45dff33bb5 | |||
bac802ec5b | |||
34a436fe49 | |||
cb2ffcf77a | |||
a27d3109ed | |||
a998c52eec | |||
8b8a280b37 | |||
3befff41fa | |||
9810435456 | |||
84a65cd1fc | |||
5bdf81b2ed | |||
c66d08b352 | |||
a4e9ad7f5a | |||
a88be86350 | |||
4baf4f8aa6 | |||
1f2b8e5c4b | |||
![]() |
8eac624bb2 | ||
976088ad03 | |||
dcb63c48e9 | |||
dcc47104ef | |||
a32d480b43 | |||
e0601379ba | |||
3c42cbb6d0 | |||
cc0f277da7 | |||
1299fe469d | |||
5d0f3eec56 | |||
![]() |
c8ada79776 | ||
43a0aa3529 | |||
9c76d469d6 | |||
3c57f207b6 | |||
77d03e5837 | |||
ed5b7e95a6 | |||
2bf7df12a8 | |||
0e23d9181e | |||
7e698eda32 | |||
6e92c428f9 | |||
b18e1ca56e | |||
1ef1d27452 | |||
7c12b757ee | |||
484888f80f | |||
4e265b66c2 | |||
6d3f4ba372 | |||
2321a1076c | |||
dd2ae7a2c8 | |||
2fc9bdee86 | |||
67190e1b4c | |||
8ab461ff72 | |||
9b5356f8e7 | |||
370402f303 | |||
7f57ed13c7 | |||
325eef57e2 | |||
956de5f9e3 | |||
48427b7923 | |||
fceb5e711c | |||
836d13386e | |||
c3daa92280 | |||
d566c285fd | |||
a9e47dbc17 | |||
4a5756f24d | |||
a250f532d9 | |||
e68411e93c | |||
b55b2a11fe | |||
7dd64c8e31 | |||
8f95479689 | |||
9712ff15bb | |||
a3cf99f3af | |||
2ee7da7995 | |||
3104dea918 | |||
cb0db7fae1 | |||
4ac7de48b0 | |||
5211821a84 | |||
29e48e284c | |||
4759bdcd33 | |||
9072e94d14 | |||
b50d56d511 | |||
1bf175b09c | |||
52fd95551c | |||
769ad22ea6 | |||
![]() |
8b36ad81ab | ||
1e32faa1d1 | |||
78d7e6f310 | |||
ab45109206 | |||
12b48a2e74 | |||
1bc3aaf53b | |||
b52cc1de29 | |||
214dd687b1 | |||
6b4da2b061 | |||
![]() |
bcf76dcd28 | ||
207a2254f3 | |||
0f39930b88 | |||
d4e97f2860 | |||
a2fa16949a | |||
91e4220397 | |||
d7fe760900 | |||
a16f02fd5f | |||
601efc6122 | |||
51807a80d0 | |||
ef14db8bbb | |||
7e0262e4fe | |||
70f1c03cb6 | |||
8d6891c4f0 | |||
c9a597ea82 | |||
c1a468148d | |||
59d4d2f728 | |||
![]() |
9ca48bbcfa | ||
3994d8d49d | |||
53124fc8d7 | |||
35fef4cb1e | |||
f1e2278695 | |||
![]() |
6a1f0013d6 | ||
6626362d23 | |||
107cea1308 | |||
7b0bc4469f | |||
e3eea751cb | |||
7b76a8fe08 | |||
4b1cf2cd9d | |||
88797d00be | |||
f98852c336 | |||
606ec428ab | |||
![]() |
136ac4c7bc | ||
![]() |
87d8882db1 | ||
915c6b5246 | |||
54f9802975 | |||
997a1645a0 | |||
fefb0f92b0 | |||
665906ecb3 | |||
781a0f4aae | |||
5f8c756dc7 | |||
8e75f13e9a | |||
82e8413e56 | |||
73cba85592 | |||
861bb001c9 | |||
08fea34366 | |||
a6fcf6da19 | |||
f730121047 | |||
80f1077b35 |
34
.gitignore
vendored
|
@ -1,13 +1,19 @@
|
|||
# Building
|
||||
build/
|
||||
dist/
|
||||
deb_dist/
|
||||
linux/flatpak/AppDir
|
||||
linux/flatpak/repo
|
||||
linux/flatpak/build-dir
|
||||
linux/flatpak/.flatpak-builder
|
||||
assets/linux/flatpak/AppDir
|
||||
assets/linux/flatpak/repo
|
||||
assets/linux/flatpak/build-dir
|
||||
assets/linux/flatpak/.flatpak-builder
|
||||
*.snap
|
||||
*.spec
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.spec
|
||||
*.egg-info/
|
||||
|
||||
# Runtime data
|
||||
**/**.qmlc
|
||||
**/**.jsc
|
||||
**/**.pyc
|
||||
|
@ -20,17 +26,21 @@ linux/flatpak/.flatpak-builder
|
|||
.DS_Store
|
||||
**/.DS_Store
|
||||
**/__pycache__/
|
||||
|
||||
# IDE Data
|
||||
.ropeproject
|
||||
.vscode
|
||||
build
|
||||
*.kdev4
|
||||
.kdev4
|
||||
docs/html
|
||||
.directory
|
||||
*.kdev4
|
||||
*.lpf
|
||||
*.lgg
|
||||
*.spec
|
||||
.kdev4
|
||||
AccountFree.pro
|
||||
AccountFree.pro.user
|
||||
*.egg-info/
|
||||
*.tar.gz
|
||||
|
||||
# Tests
|
||||
common/coverage/
|
||||
**/.coverage
|
||||
|
||||
# npm
|
||||
common/node_modules
|
||||
runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/index.mjs*
|
||||
|
|
4
.gitmodules
vendored
|
@ -1,3 +1,3 @@
|
|||
[submodule "LogarithmPlotter/qml/eu/ad5001/MixedMenu"]
|
||||
path = LogarithmPlotter/qml/eu/ad5001/MixedMenu
|
||||
[submodule "runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/MixedMenu"]
|
||||
path = runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/MixedMenu
|
||||
url = https://git.ad5001.eu/Ad5001/MixedMenu
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Changelog
|
||||
|
||||
## v0.5.0 (11 Jan 2023)
|
||||
## v0.5.0 (11 Jan 2024)
|
||||
|
||||
**New**
|
||||
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
#!/bin/bash
|
||||
lrelease *.ts
|
|
@ -1,2 +0,0 @@
|
|||
#!/bin/bash
|
||||
lupdate -extensions js,qs,qml,py -recursive .. -ts lp_*.ts
|
|
@ -1,155 +0,0 @@
|
|||
"""
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from time import time
|
||||
|
||||
from PySide6.QtWidgets import QApplication
|
||||
from PySide6.QtQml import QQmlApplicationEngine
|
||||
from PySide6.QtCore import Qt, QTranslator, QLocale
|
||||
from PySide6.QtGui import QIcon
|
||||
|
||||
from tempfile import TemporaryDirectory
|
||||
from os import getcwd, chdir, environ, path, remove, close
|
||||
from platform import release as os_release
|
||||
from sys import platform, argv, version as sys_version, exit
|
||||
from sys import path as sys_path
|
||||
|
||||
start_time = time()
|
||||
|
||||
# Create the temporary directory for saving copied screenshots and latex files
|
||||
tempdir = TemporaryDirectory()
|
||||
tmpfile = path.join(tempdir.name, 'graph.png')
|
||||
pwd = getcwd()
|
||||
|
||||
chdir(path.dirname(path.realpath(__file__)))
|
||||
|
||||
if path.realpath(path.join(getcwd(), "..")) not in sys_path:
|
||||
sys_path.append(path.realpath(path.join(getcwd(), "..")))
|
||||
|
||||
|
||||
from LogarithmPlotter import __VERSION__
|
||||
from LogarithmPlotter.util import config, native
|
||||
from LogarithmPlotter.util.update import check_for_updates
|
||||
from LogarithmPlotter.util.helper import Helper
|
||||
from LogarithmPlotter.util.latex import Latex
|
||||
|
||||
config.init()
|
||||
|
||||
def get_linux_theme():
|
||||
des = {
|
||||
"KDE": "Fusion",
|
||||
"gnome": "Basic",
|
||||
"lxqt": "Fusion",
|
||||
"mate": "Fusion",
|
||||
}
|
||||
if "XDG_SESSION_DESKTOP" in environ:
|
||||
return des[environ["XDG_SESSION_DESKTOP"]] if environ["XDG_SESSION_DESKTOP"] in des else "Fusion"
|
||||
else:
|
||||
# Android
|
||||
return "Material"
|
||||
|
||||
def run():
|
||||
|
||||
if not 'QT_QUICK_CONTROLS_STYLE' in environ:
|
||||
environ["QT_QUICK_CONTROLS_STYLE"] = {
|
||||
"linux": get_linux_theme(),
|
||||
"freebsd": get_linux_theme(),
|
||||
"win32": "Universal" if os_release == "10" else "Fusion",
|
||||
"cygwin": "Fusion",
|
||||
"darwin": "macOS"
|
||||
}[platform]
|
||||
|
||||
dep_time = time()
|
||||
print("Loaded dependencies in " + str((dep_time - start_time)*1000) + "ms.")
|
||||
|
||||
icon_fallbacks = QIcon.fallbackSearchPaths();
|
||||
base_icon_path = path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "icons")
|
||||
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "common")))
|
||||
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "objects")))
|
||||
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "history")))
|
||||
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "settings")))
|
||||
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "settings", "custom")))
|
||||
QIcon.setFallbackSearchPaths(icon_fallbacks);
|
||||
|
||||
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
|
||||
app = QApplication(argv)
|
||||
app.setApplicationName("LogarithmPlotter")
|
||||
app.setOrganizationName("Ad5001")
|
||||
app.styleHints().setShowShortcutsInContextMenus(True)
|
||||
app.setWindowIcon(QIcon(path.realpath(path.join(getcwd(), "logarithmplotter.svg"))))
|
||||
|
||||
# Installing translators
|
||||
translator = QTranslator()
|
||||
# Check if lang is forced.
|
||||
forcedlang = [p for p in argv if p[:7]=="--lang="]
|
||||
locale = QLocale(forcedlang[0][7:]) if len(forcedlang) > 0 else QLocale()
|
||||
if (translator.load(locale, "lp", "_", path.realpath(path.join(getcwd(), "i18n")))):
|
||||
app.installTranslator(translator);
|
||||
|
||||
# Installing macOS file handler.
|
||||
macOSFileOpenHandler = None
|
||||
if platform == "darwin":
|
||||
macOSFileOpenHandler = native.MacOSFileOpenHandler()
|
||||
app.installEventFilter(macOSFileOpenHandler)
|
||||
|
||||
engine = QQmlApplicationEngine()
|
||||
global tmpfile
|
||||
helper = Helper(pwd, tmpfile)
|
||||
latex = Latex(tempdir)
|
||||
engine.rootContext().setContextProperty("Helper", helper)
|
||||
engine.rootContext().setContextProperty("Latex", latex)
|
||||
engine.rootContext().setContextProperty("TestBuild", "--test-build" in argv)
|
||||
engine.rootContext().setContextProperty("StartTime", dep_time)
|
||||
|
||||
app.translate("About","About LogarithmPlotter") # FOR SOME REASON, if this isn't included, Qt refuses to load the QML file.
|
||||
|
||||
engine.addImportPath(path.realpath(path.join(getcwd(), "qml")))
|
||||
engine.load(path.realpath(path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "LogarithmPlotter.qml")))
|
||||
|
||||
|
||||
if not engine.rootObjects():
|
||||
print("No root object", path.realpath(path.join(getcwd(), "qml")))
|
||||
print(path.realpath(path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "LogarithmPlotter.qml")))
|
||||
exit(-1)
|
||||
|
||||
# Open the current diagram
|
||||
chdir(pwd)
|
||||
if len(argv) > 0 and path.exists(argv[-1]) and argv[-1].split('.')[-1] in ['lpf']:
|
||||
engine.rootObjects()[0].loadDiagram(argv[-1])
|
||||
chdir(path.dirname(path.realpath(__file__)))
|
||||
|
||||
if platform == "darwin":
|
||||
macOSFileOpenHandler.init_graphics(engine.rootObjects()[0])
|
||||
|
||||
# Check for LaTeX installation if LaTeX support is enabled
|
||||
if config.getSetting("enable_latex"):
|
||||
latex.check_latex_install()
|
||||
|
||||
# Check for updates
|
||||
if config.getSetting("check_for_updates"):
|
||||
check_for_updates(__VERSION__, engine.rootObjects()[0])
|
||||
|
||||
exit_code = app.exec()
|
||||
|
||||
tempdir.cleanup()
|
||||
config.save()
|
||||
exit(exit_code)
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
|
|
@ -1,221 +0,0 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
import QtQml
|
||||
import QtQuick.Window
|
||||
import "../js/objects.js" as Objects
|
||||
import "../js/historylib.js" as HistoryLib
|
||||
import "../js/history/common.js" as HistoryCommon
|
||||
|
||||
/*!
|
||||
\qmltype History
|
||||
\inqmlmodule eu.ad5001.LogarithmPlotter.History
|
||||
\brief QObject holding persistantly for undo & redo stacks.
|
||||
|
||||
\sa HistoryBrowser, historylib
|
||||
*/
|
||||
Item {
|
||||
// Using a QtObject is necessary in order to have proper property propagation in QML
|
||||
id: historyObj
|
||||
|
||||
/*!
|
||||
\qmlproperty int History::undoCount
|
||||
Count of undo actions.
|
||||
*/
|
||||
property int undoCount: 0
|
||||
/*!
|
||||
\qmlproperty int History::redoCount
|
||||
Count of redo actions.
|
||||
*/
|
||||
property int redoCount: 0
|
||||
/*!
|
||||
\qmlproperty var History::undoStack
|
||||
Stack of undo actions.
|
||||
*/
|
||||
property var undoStack: []
|
||||
/*!
|
||||
\qmlproperty var History::redoStack
|
||||
Stack of redo actions.
|
||||
*/
|
||||
property var redoStack: []
|
||||
/*!
|
||||
\qmlproperty bool History::saved
|
||||
true when no modification was done to the current working file, false otherwise.
|
||||
*/
|
||||
property bool saved: true
|
||||
|
||||
|
||||
/*!
|
||||
\qmlmethod void History::clear()
|
||||
Clears both undo and redo stacks completly.
|
||||
*/
|
||||
function clear() {
|
||||
undoCount = 0
|
||||
redoCount = 0
|
||||
undoStack = []
|
||||
redoStack = []
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
\qmlmethod var History::serialize()
|
||||
Serializes history into JSON-able content.
|
||||
*/
|
||||
function serialize() {
|
||||
let undoSt = [], redoSt = [];
|
||||
for(let i = 0; i < undoCount; i++)
|
||||
undoSt.push([
|
||||
undoStack[i].type(),
|
||||
undoStack[i].export()
|
||||
]);
|
||||
for(let i = 0; i < redoCount; i++)
|
||||
redoSt.push([
|
||||
redoStack[i].type(),
|
||||
redoStack[i].export()
|
||||
]);
|
||||
return [undoSt, redoSt]
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod void History::unserialize(var undoSt, var redoSt)
|
||||
Unserializes both \c undoSt stack and \c redoSt stack from serialized content.
|
||||
*/
|
||||
function unserialize(undoSt, redoSt) {
|
||||
clear();
|
||||
for(let i = 0; i < undoSt.length; i++)
|
||||
undoStack.push(new HistoryLib.Actions[undoSt[i][0]](...undoSt[i][1]))
|
||||
for(let i = 0; i < redoSt.length; i++)
|
||||
redoStack.push(new HistoryLib.Actions[redoSt[i][0]](...redoSt[i][1]))
|
||||
undoCount = undoSt.length;
|
||||
redoCount = redoSt.length;
|
||||
objectLists.update()
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod void History::addToHistory(var action)
|
||||
Adds an instance of historylib.Action to history.
|
||||
*/
|
||||
function addToHistory(action) {
|
||||
if(action instanceof HistoryLib.Action) {
|
||||
console.log("Added new entry to history: " + action.getReadableString())
|
||||
undoStack.push(action)
|
||||
undoCount++;
|
||||
if(Helper.getSettingBool("reset_redo_stack")) {
|
||||
redoStack = []
|
||||
redoCount = 0
|
||||
}
|
||||
saved = false
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod void History::undo(bool updateObjectList = true)
|
||||
Undoes the historylib.Action at the top of the undo stack and pushes it to the top of the redo stack.
|
||||
By default, will update the graph and the object list. This behavior can be disabled by setting the \c updateObjectList to false.
|
||||
*/
|
||||
function undo(updateObjectList = true) {
|
||||
if(undoStack.length > 0) {
|
||||
var action = undoStack.pop()
|
||||
action.undo()
|
||||
if(updateObjectList)
|
||||
objectLists.update()
|
||||
redoStack.push(action)
|
||||
undoCount--;
|
||||
redoCount++;
|
||||
saved = false
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod void History::redo(bool updateObjectList = true)
|
||||
Redoes the historylib.Action at the top of the redo stack and pushes it to the top of the undo stack.
|
||||
By default, will update the graph and the object list. This behavior can be disabled by setting the \c updateObjectList to false.
|
||||
*/
|
||||
function redo(updateObjectList = true) {
|
||||
if(redoStack.length > 0) {
|
||||
var action = redoStack.pop()
|
||||
action.redo()
|
||||
if(updateObjectList)
|
||||
objectLists.update()
|
||||
undoStack.push(action)
|
||||
undoCount++;
|
||||
redoCount--;
|
||||
saved = false
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod void History::undoMultipleDefered(int toUndoCount)
|
||||
Undoes several historylib.Action at the top of the undo stack and pushes them to the top of the redo stack.
|
||||
It undoes them deferedly to avoid overwhelming the computer while creating a cool short accelerated summary of all changes.
|
||||
*/
|
||||
function undoMultipleDefered(toUndoCount) {
|
||||
undoTimer.toUndoCount = toUndoCount;
|
||||
undoTimer.start()
|
||||
if(toUndoCount > 0)
|
||||
saved = false
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
\qmlmethod void History::redoMultipleDefered(int toRedoCount)
|
||||
Redoes several historylib.Action at the top of the redo stack and pushes them to the top of the undo stack.
|
||||
It redoes them deferedly to avoid overwhelming the computer while creating a cool short accelerated summary of all changes.
|
||||
*/
|
||||
function redoMultipleDefered(toRedoCount) {
|
||||
redoTimer.toRedoCount = toRedoCount;
|
||||
redoTimer.start()
|
||||
if(toRedoCount > 0)
|
||||
saved = false
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: undoTimer
|
||||
interval: 5; running: false; repeat: true
|
||||
property int toUndoCount: 0
|
||||
onTriggered: {
|
||||
if(toUndoCount > 0) {
|
||||
historyObj.undo(toUndoCount % 4 == 1) // Only redraw once every 4 changes.
|
||||
toUndoCount--;
|
||||
} else {
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: redoTimer
|
||||
interval: 5; running: false; repeat: true
|
||||
property int toRedoCount: 0
|
||||
onTriggered: {
|
||||
if(toRedoCount > 0) {
|
||||
historyObj.redo(toRedoCount % 4 == 1) // Only redraw once every 4 changes.
|
||||
toRedoCount--;
|
||||
} else {
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
HistoryLib.history = historyObj
|
||||
HistoryCommon.themeTextColor = sysPalette.windowText
|
||||
HistoryCommon.imageDepth = Screen.devicePixelRatio
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
module eu.ad5001.LogarithmPlotter.History
|
||||
|
||||
History 1.0 History.qml
|
||||
HistoryBrowser 1.0 HistoryBrowser.qml
|
||||
HistoryItem 1.0 HistoryItem.qml
|
|
@ -1,493 +0,0 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
import Qt.labs.platform as Native
|
||||
import "js/objects.js" as Objects
|
||||
import "js/utils.js" as Utils
|
||||
import "js/mathlib.js" as MathLib
|
||||
|
||||
/*!
|
||||
\qmltype LogGraphCanvas
|
||||
\inqmlmodule eu.ad5001.LogarithmPlotter
|
||||
\brief Canvas used to display the diagram.
|
||||
|
||||
Provides a customized canvas with several helper methods to be used by objects.
|
||||
|
||||
\sa LogarithmPlotter, PickLocationOverlay
|
||||
*/
|
||||
Canvas {
|
||||
id: canvas
|
||||
anchors.top: separator.bottom
|
||||
anchors.left: parent.left
|
||||
height: parent.height - 90
|
||||
width: parent.width
|
||||
|
||||
/*!
|
||||
\qmlproperty double LogGraphCanvas::xmin
|
||||
Minimum x of the diagram, provided from settings.
|
||||
\sa Settings
|
||||
*/
|
||||
property double xmin: 0
|
||||
/*!
|
||||
\qmlproperty double LogGraphCanvas::ymax
|
||||
Maximum y of the diagram, provided from settings.
|
||||
\sa Settings
|
||||
*/
|
||||
property double ymax: 0
|
||||
/*!
|
||||
\qmlproperty double LogGraphCanvas::xzoom
|
||||
Zoom on the x axis of the diagram, provided from settings.
|
||||
\sa Settings
|
||||
*/
|
||||
property double xzoom: 10
|
||||
/*!
|
||||
\qmlproperty double LogGraphCanvas::yzoom
|
||||
Zoom on the y axis of the diagram, provided from settings.
|
||||
\sa Settings
|
||||
*/
|
||||
property double yzoom: 10
|
||||
/*!
|
||||
\qmlproperty string LogGraphCanvas::xaxisstep
|
||||
Step of the x axis graduation, provided from settings.
|
||||
\note: Only available in non-logarithmic mode.
|
||||
\sa Settings
|
||||
*/
|
||||
property string xaxisstep: "4"
|
||||
/*!
|
||||
\qmlproperty string LogGraphCanvas::yaxisstep
|
||||
Step of the y axis graduation, provided from settings.
|
||||
\sa Settings
|
||||
*/
|
||||
property string yaxisstep: "4"
|
||||
/*!
|
||||
\qmlproperty string LogGraphCanvas::xlabel
|
||||
Label used on the x axis, provided from settings.
|
||||
\sa Settings
|
||||
*/
|
||||
property string xlabel: ""
|
||||
/*!
|
||||
\qmlproperty string LogGraphCanvas::ylabel
|
||||
Label used on the y axis, provided from settings.
|
||||
\sa Settings
|
||||
*/
|
||||
property string ylabel: ""
|
||||
/*!
|
||||
\qmlproperty double LogGraphCanvas::linewidth
|
||||
Width of lines that will be drawn into the canvas, provided from settings.
|
||||
\sa Settings
|
||||
*/
|
||||
property double linewidth: 1
|
||||
/*!
|
||||
\qmlproperty double LogGraphCanvas::textsize
|
||||
Font size of the text that will be drawn into the canvas, provided from settings.
|
||||
\sa Settings
|
||||
*/
|
||||
property double textsize: 14
|
||||
/*!
|
||||
\qmlproperty bool LogGraphCanvas::logscalex
|
||||
true if the canvas should be in logarithmic mode, false otherwise.
|
||||
Provided from settings.
|
||||
\sa Settings
|
||||
*/
|
||||
property bool logscalex: false
|
||||
/*!
|
||||
\qmlproperty bool LogGraphCanvas::showxgrad
|
||||
true if the x graduation should be shown, false otherwise.
|
||||
Provided from settings.
|
||||
\sa Settings
|
||||
*/
|
||||
property bool showxgrad: false
|
||||
/*!
|
||||
\qmlproperty bool LogGraphCanvas::showygrad
|
||||
true if the y graduation should be shown, false otherwise.
|
||||
Provided from settings.
|
||||
\sa Settings
|
||||
*/
|
||||
property bool showygrad: false
|
||||
|
||||
/*!
|
||||
\qmlproperty int LogGraphCanvas::maxgradx
|
||||
Max power of the logarithmic scaled on the x axis in logarithmic mode.
|
||||
*/
|
||||
property int maxgradx: 20
|
||||
|
||||
/*!
|
||||
\qmlproperty var LogGraphCanvas::yaxisstepExpr
|
||||
Expression for the y axis step (used to create labels).
|
||||
*/
|
||||
property var yaxisstepExpr: (new MathLib.Expression(`x*(${yaxisstep})`))
|
||||
/*!
|
||||
\qmlproperty double LogGraphCanvas::yaxisstep1
|
||||
Value of the for the y axis step.
|
||||
*/
|
||||
property double yaxisstep1: yaxisstepExpr.execute(1)
|
||||
/*!
|
||||
\qmlproperty int LogGraphCanvas::drawMaxY
|
||||
Minimum value of y that should be drawn onto the canvas.
|
||||
*/
|
||||
property int drawMaxY: Math.ceil(Math.max(Math.abs(ymax), Math.abs(px2y(canvasSize.height)))/yaxisstep1)
|
||||
/*!
|
||||
\qmlproperty var LogGraphCanvas::xaxisstepExpr
|
||||
Expression for the x axis step (used to create labels).
|
||||
*/
|
||||
property var xaxisstepExpr: (new MathLib.Expression(`x*(${xaxisstep})`))
|
||||
/*!
|
||||
\qmlproperty double LogGraphCanvas::xaxisstep1
|
||||
Value of the for the x axis step.
|
||||
*/
|
||||
property double xaxisstep1: xaxisstepExpr.execute(1)
|
||||
/*!
|
||||
\qmlproperty int LogGraphCanvas::drawMaxX
|
||||
Maximum value of x that should be drawn onto the canvas.
|
||||
*/
|
||||
property int drawMaxX: Math.ceil(Math.max(Math.abs(xmin), Math.abs(px2x(canvasSize.width)))/xaxisstep1)
|
||||
|
||||
/*!
|
||||
\qmlproperty var LogGraphCanvas::imageLoaders
|
||||
Dictionary of format {image: [callback.image data]} containing data for defered image loading.
|
||||
*/
|
||||
property var imageLoaders: {}
|
||||
/*!
|
||||
\qmlproperty var LogGraphCanvas::ctx
|
||||
Cache for the 2D context so that it may be used asynchronously.
|
||||
*/
|
||||
property var ctx
|
||||
|
||||
Component.onCompleted: imageLoaders = {}
|
||||
|
||||
Native.MessageDialog {
|
||||
id: drawingErrorDialog
|
||||
title: qsTranslate("expression", "LogarithmPlotter - Drawing error")
|
||||
text: ""
|
||||
function showDialog(objType, objName, error) {
|
||||
text = qsTranslate("error", "Error while attempting to draw %1 %2:\n%3\n\nUndoing last change.").arg(objType).arg(objName).arg(error)
|
||||
open()
|
||||
}
|
||||
}
|
||||
|
||||
onPaint: function(rect) {
|
||||
//console.log('Redrawing')
|
||||
if(rect.width == canvas.width) { // Redraw full canvas
|
||||
ctx = getContext("2d");
|
||||
reset(ctx)
|
||||
drawGrille(ctx)
|
||||
drawAxises(ctx)
|
||||
drawLabels(ctx)
|
||||
ctx.lineWidth = linewidth
|
||||
for(var objType in Objects.currentObjects) {
|
||||
for(var obj of Objects.currentObjects[objType]){
|
||||
ctx.strokeStyle = obj.color
|
||||
ctx.fillStyle = obj.color
|
||||
if(obj.visible)
|
||||
try {
|
||||
obj.draw(canvas, ctx)
|
||||
} catch(e) {
|
||||
// Drawing throws an error. Generally, it's due to a new modification (or the opening of a file)
|
||||
drawingErrorDialog.showDialog(objType, obj.name, e.message)
|
||||
history.undo()
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.lineWidth = 1
|
||||
}
|
||||
}
|
||||
|
||||
onImageLoaded: {
|
||||
Object.keys(imageLoaders).forEach((key) => {
|
||||
if(isImageLoaded(key)) {
|
||||
// Calling callback
|
||||
imageLoaders[key][0](canvas, ctx, imageLoaders[key][1])
|
||||
delete imageLoaders[key]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod void LogGraphCanvas::reset(var ctx)
|
||||
Resets the canvas to a blank one with default setting using 2D \c ctx.
|
||||
*/
|
||||
function reset(ctx){
|
||||
// Reset
|
||||
ctx.fillStyle = "#FFFFFF"
|
||||
ctx.strokeStyle = "#000000"
|
||||
ctx.font = `${canvas.textsize}px sans-serif`
|
||||
ctx.fillRect(0,0,width,height)
|
||||
}
|
||||
|
||||
// Drawing the log based graph
|
||||
|
||||
/*!
|
||||
\qmlmethod void LogGraphCanvas::drawGrille(var ctx)
|
||||
Draws the grid using 2D \c ctx.
|
||||
*/
|
||||
function drawGrille(ctx) {
|
||||
ctx.strokeStyle = "#C0C0C0"
|
||||
if(logscalex) {
|
||||
for(var xpow = -maxgradx; xpow <= maxgradx; xpow++) {
|
||||
for(var xmulti = 1; xmulti < 10; xmulti++) {
|
||||
drawXLine(ctx, Math.pow(10, xpow)*xmulti)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(var x = 0; x < drawMaxX; x+=1) {
|
||||
drawXLine(ctx, x*xaxisstep1)
|
||||
drawXLine(ctx, -x*xaxisstep1)
|
||||
}
|
||||
}
|
||||
for(var y = 0; y < drawMaxY; y+=1) {
|
||||
drawYLine(ctx, y*yaxisstep1)
|
||||
drawYLine(ctx, -y*yaxisstep1)
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod void LogGraphCanvas::drawAxises(var ctx)
|
||||
Draws the graph axises using 2D \c ctx.
|
||||
*/
|
||||
function drawAxises(ctx) {
|
||||
ctx.strokeStyle = "#000000"
|
||||
var axisypos = logscalex ? 1 : 0
|
||||
drawXLine(ctx, axisypos)
|
||||
drawYLine(ctx, 0)
|
||||
var axisypx = x2px(axisypos) // X coordinate of Y axis
|
||||
var axisxpx = y2px(0) // Y coordinate of X axis
|
||||
// Drawing arrows
|
||||
drawLine(ctx, axisypx, 0, axisypx-10, 10)
|
||||
drawLine(ctx, axisypx, 0, axisypx+10, 10)
|
||||
drawLine(ctx, canvasSize.width, axisxpx, canvasSize.width-10, axisxpx-10)
|
||||
drawLine(ctx, canvasSize.width, axisxpx, canvasSize.width-10, axisxpx+10)
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod void LogGraphCanvas::drawLabels(var ctx)
|
||||
Draws all labels (graduation & axises labels) using 2D \c ctx.
|
||||
*/
|
||||
function drawLabels(ctx) {
|
||||
var axisypx = x2px(logscalex ? 1 : 0) // X coordinate of Y axis
|
||||
var axisxpx = y2px(0) // Y coordinate of X axis
|
||||
// Labels
|
||||
ctx.fillStyle = "#000000"
|
||||
ctx.font = `${canvas.textsize}px sans-serif`
|
||||
ctx.fillText(ylabel, axisypx+10, 24)
|
||||
var textSize = ctx.measureText(xlabel).width
|
||||
ctx.fillText(xlabel, canvasSize.width-14-textSize, axisxpx-5)
|
||||
// Axis graduation labels
|
||||
ctx.font = `${canvas.textsize-4}px sans-serif`
|
||||
|
||||
var txtMinus = ctx.measureText('-').width
|
||||
if(showxgrad) {
|
||||
if(logscalex) {
|
||||
for(var xpow = -maxgradx; xpow <= maxgradx; xpow+=1) {
|
||||
var textSize = ctx.measureText("10"+Utils.textsup(xpow)).width
|
||||
if(xpow != 0)
|
||||
drawVisibleText(ctx, "10"+Utils.textsup(xpow), x2px(Math.pow(10,xpow))-textSize/2, axisxpx+16+(6*(xpow==1)))
|
||||
}
|
||||
} else {
|
||||
for(var x = 1; x < drawMaxX; x += 1) {
|
||||
var drawX = x*xaxisstep1
|
||||
var txtX = xaxisstepExpr.simplify(x).replace(/^\((.+)\)$/, '$1')
|
||||
var textSize = measureText(ctx, txtX, 6).height
|
||||
drawVisibleText(ctx, txtX, x2px(drawX)-4, axisxpx+textsize/2+textSize)
|
||||
drawVisibleText(ctx, '-'+txtX, x2px(-drawX)-4, axisxpx+textsize/2+textSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
if(showygrad) {
|
||||
for(var y = 0; y < drawMaxY; y += 1) {
|
||||
var drawY = y*yaxisstep1
|
||||
var txtY = yaxisstepExpr.simplify(y).replace(/^\((.+)\)$/, '$1')
|
||||
var textSize = ctx.measureText(txtY).width
|
||||
drawVisibleText(ctx, txtY, axisypx-6-textSize, y2px(drawY)+4+(10*(y==0)))
|
||||
if(y != 0)
|
||||
drawVisibleText(ctx, '-'+txtY, axisypx-6-textSize-txtMinus, y2px(-drawY)+4)
|
||||
}
|
||||
}
|
||||
ctx.fillStyle = "#FFFFFF"
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod void LogGraphCanvas::drawXLine(var ctx, double x)
|
||||
Draws an horizontal line at \c x plot coordinate using 2D \c ctx.
|
||||
*/
|
||||
function drawXLine(ctx, x) {
|
||||
if(isVisible(x, ymax)) {
|
||||
drawLine(ctx, x2px(x), 0, x2px(x), canvasSize.height)
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod void LogGraphCanvas::drawXLine(var ctx, double x)
|
||||
Draws an vertical line at \c y plot coordinate using 2D \c ctx.
|
||||
*/
|
||||
function drawYLine(ctx, y) {
|
||||
if(isVisible(xmin, y)) {
|
||||
drawLine(ctx, 0, y2px(y), canvasSize.width, y2px(y))
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod void LogGraphCanvas::drawVisibleText(var ctx, string text, double x, double y)
|
||||
Writes multline \c text onto the canvas using 2D \c ctx.
|
||||
\note The \c x and \c y properties here are relative to the canvas, not the plot.
|
||||
*/
|
||||
function drawVisibleText(ctx, text, x, y) {
|
||||
if(x > 0 && x < canvasSize.width && y > 0 && y < canvasSize.height) {
|
||||
text.toString().split("\n").forEach(function(txt, i){
|
||||
ctx.fillText(txt, x, y+(canvas.textsize*i))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod void LogGraphCanvas::drawVisibleImage(var ctx, var image, double x, double y)
|
||||
Draws an \c image onto the canvas using 2D \c ctx.
|
||||
\note The \c x, \c y \c width and \c height properties here are relative to the canvas, not the plot.
|
||||
*/
|
||||
function drawVisibleImage(ctx, image, x, y, width, height) {
|
||||
//console.log("Drawing image", isImageLoaded(image), isImageError(image))
|
||||
markDirty(Qt.rect(x, y, width, height));
|
||||
ctx.drawImage(image, x, y, width, height)
|
||||
/*if(true || (x > 0 && x < canvasSize.width && y > 0 && y < canvasSize.height)) {
|
||||
}*/
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod var LogGraphCanvas::measureText(var ctx, string text)
|
||||
Measures the wicth and height of a multiline \c text that would be drawn onto the canvas using 2D \c ctx.
|
||||
Return format: dictionary {"width": width, "height": height}
|
||||
*/
|
||||
function measureText(ctx, text) {
|
||||
let theight = 0
|
||||
let twidth = 0
|
||||
let defaultHeight = ctx.measureText("M").width // Approximate but good enough!
|
||||
text.split("\n").forEach(function(txt, i){
|
||||
theight += defaultHeight
|
||||
if(ctx.measureText(txt).width > twidth) twidth = ctx.measureText(txt).width
|
||||
})
|
||||
return {'width': twidth, 'height': theight}
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod double LogGraphCanvas::x2px(double x)
|
||||
Converts an \c x coordinate to it's relative position on the canvas.
|
||||
It supports both logarithmic and non logarithmic scale depending on the currently selected mode.
|
||||
*/
|
||||
function x2px(x) {
|
||||
if(logscalex) {
|
||||
var logxmin = Math.log(xmin)
|
||||
return (Math.log(x)-logxmin)*xzoom
|
||||
} else return (x - xmin)*xzoom
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod double LogGraphCanvas::y2px(double y)
|
||||
Converts an \c y coordinate to it's relative position on the canvas.
|
||||
The y axis not supporting logarithmic scale, it only support linear convertion.
|
||||
*/
|
||||
function y2px(y) {
|
||||
return (ymax-y)*yzoom
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod double LogGraphCanvas::px2x(double px)
|
||||
Converts an x \c px position on the canvas to it's corresponding coordinate on the plot.
|
||||
It supports both logarithmic and non logarithmic scale depending on the currently selected mode.
|
||||
*/
|
||||
function px2x(px) {
|
||||
if(logscalex) {
|
||||
return Math.exp(px/xzoom+Math.log(xmin))
|
||||
} else return (px/xzoom+xmin)
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod double LogGraphCanvas::px2x(double px)
|
||||
Converts an x \c px position on the canvas to it's corresponding coordinate on the plot.
|
||||
It supports both logarithmic and non logarithmic scale depending on the currently selected mode.
|
||||
*/
|
||||
function px2y(px) {
|
||||
return -(px/yzoom-ymax)
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod bool LogGraphCanvas::isVisible(double x, double y)
|
||||
Checks whether a plot point (\c x, \c y) is visible or not on the canvas.
|
||||
*/
|
||||
function isVisible(x, y) {
|
||||
return (x2px(x) >= 0 && x2px(x) <= canvasSize.width) && (y2px(y) >= 0 && y2px(y) <= canvasSize.height)
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod bool LogGraphCanvas::drawLine(var ctx, double x1, double y1, double x2, double y2)
|
||||
Draws a line from plot point (\c x1, \c y1) to plot point (\c x2, \¢ y2) using 2D \c ctx.
|
||||
*/
|
||||
function drawLine(ctx, x1, y1, x2, y2) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x1, y1);
|
||||
ctx.lineTo(x2, y2);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod bool LogGraphCanvas::drawDashedLine2(var ctx, double x1, double y1, double x2, double y2)
|
||||
Draws a dashed line from plot point (\c x1, \c y1) to plot point (\c x2, \¢ y2) using 2D \c ctx.
|
||||
*/
|
||||
function drawDashedLine2(ctx, x1, y1, x2, y2, dashPxSize = 5) {
|
||||
ctx.setLineDash([dashPxSize, dashPxSize]);
|
||||
drawLine(ctx, x1, y1, x2, y2)
|
||||
ctx.setLineDash([]);
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod bool LogGraphCanvas::drawDashedLine(var ctx, double x1, double y1, double x2, double y2)
|
||||
Draws a dashed line from plot point (\c x1, \c y1) to plot point (\c x2, \¢ y2) using 2D \c ctx.
|
||||
(Legacy slower method)
|
||||
*/
|
||||
function drawDashedLine(ctx, x1, y1, x2, y2, dashPxSize = 10) {
|
||||
var distance = Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2))
|
||||
var progPerc = dashPxSize/distance
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x1, y1);
|
||||
for(var i = 0; i < 1; i += progPerc) {
|
||||
ctx.lineTo(x1-(x1-x2)*i, y1-(y1-y2)*i)
|
||||
ctx.moveTo(x1-(x1-x2)*(i+progPerc/2), y1-(y1-y2)*(i+progPerc/2))
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod var LogGraphCanvas::renderLatexImage(string ltxText, color)
|
||||
Renders latex markup \c ltxText to an image and loads it. Returns a dictionary with three values: source, width and height.
|
||||
*/
|
||||
function renderLatexImage(ltxText, color, callback) {
|
||||
let [ltxSrc, ltxWidth, ltxHeight] = Latex.render(ltxText, textsize, color).split(",")
|
||||
let imgData = {
|
||||
"source": ltxSrc,
|
||||
"width": parseFloat(ltxWidth),
|
||||
"height": parseFloat(ltxHeight)
|
||||
};
|
||||
if(!isImageLoaded(ltxSrc) && !isImageLoading(ltxSrc)){
|
||||
// Wait until the image is loaded to callback.
|
||||
loadImage(ltxSrc)
|
||||
imageLoaders[ltxSrc] = [callback, imgData]
|
||||
} else {
|
||||
// Callback directly
|
||||
callback(canvas, ctx, imgData)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,378 +0,0 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import QtQml
|
||||
import QtQuick.Controls
|
||||
import eu.ad5001.MixedMenu 1.1
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick
|
||||
// Auto loading all objects.
|
||||
import "js/objs/autoload.js" as ALObjects
|
||||
|
||||
import "js/objects.js" as Objects
|
||||
import "js/math/latex.js" as LatexJS
|
||||
import eu.ad5001.LogarithmPlotter.History 1.0
|
||||
import eu.ad5001.LogarithmPlotter.ObjectLists 1.0
|
||||
import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup
|
||||
|
||||
/*!
|
||||
\qmltype LogarithmPlotter
|
||||
\inqmlmodule eu.ad5001.LogarithmPlotter
|
||||
\brief Main window of LogarithmPlotter
|
||||
|
||||
\sa AppMenuBar, History, GreetScreen, Changelog, Alert, ObjectLists, Settings, HistoryBrowser, LogGraphCanvas, PickLocationOverlay.
|
||||
*/
|
||||
ApplicationWindow {
|
||||
id: root
|
||||
visible: true
|
||||
width: 1000
|
||||
height: 500
|
||||
color: sysPalette.window
|
||||
title: "LogarithmPlotter " + (settings.saveFilename != "" ? " - " + settings.saveFilename.split('/').pop() : "") + (history.saved ? "" : "*")
|
||||
|
||||
SystemPalette {
|
||||
id: sysPalette; colorGroup: SystemPalette.Active
|
||||
|
||||
Component.onCompleted: {
|
||||
// LatexJS initialization.
|
||||
LatexJS.enabled = Helper.getSettingBool("enable_latex")
|
||||
LatexJS.Renderer = Latex
|
||||
LatexJS.defaultColor = sysPalette.windowText
|
||||
}
|
||||
}
|
||||
SystemPalette { id: sysPaletteIn; colorGroup: SystemPalette.Disabled }
|
||||
|
||||
menuBar: appMenu.trueItem
|
||||
|
||||
AppMenuBar {id: appMenu}
|
||||
|
||||
History { id: history }
|
||||
|
||||
Popup.GreetScreen {}
|
||||
|
||||
Popup.Changelog {id: changelog}
|
||||
|
||||
Popup.About {id: about}
|
||||
|
||||
Popup.ThanksTo {id: thanksTo}
|
||||
|
||||
Popup.Alert {
|
||||
id: alert
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 5
|
||||
z: 3
|
||||
}
|
||||
|
||||
Item {
|
||||
id: sidebar
|
||||
width: 300
|
||||
height: parent.height
|
||||
//y: root.menuBar.height
|
||||
readonly property bool inPortrait: root.width < root.height
|
||||
/*modal: true// inPortrait
|
||||
interactive: inPortrait
|
||||
position: inPortrait ? 0 : 1
|
||||
*/
|
||||
visible: !inPortrait
|
||||
|
||||
|
||||
TabBar {
|
||||
id: sidebarSelector
|
||||
width: parent.width
|
||||
anchors.top: parent.top
|
||||
TabButton {
|
||||
text: qsTr("Objects")
|
||||
icon.name: 'polygon-add-nodes'
|
||||
icon.color: sysPalette.windowText
|
||||
//height: 24
|
||||
}
|
||||
TabButton {
|
||||
text: qsTr("Settings")
|
||||
icon.name: 'preferences-system-symbolic'
|
||||
icon.color: sysPalette.windowText
|
||||
//height: 24
|
||||
}
|
||||
TabButton {
|
||||
text: qsTr("History")
|
||||
icon.name: 'view-history'
|
||||
icon.color: sysPalette.windowText
|
||||
//height: 24
|
||||
}
|
||||
}
|
||||
|
||||
StackLayout {
|
||||
id: sidebarContents
|
||||
anchors.top: sidebarSelector.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.topMargin: 5
|
||||
anchors.leftMargin: 5
|
||||
anchors.bottom: parent.bottom
|
||||
//anchors.bottomMargin: sidebarSelector.height
|
||||
width: parent.width - 5
|
||||
currentIndex: sidebarSelector.currentIndex
|
||||
z: -1
|
||||
clip: true
|
||||
|
||||
ObjectLists {
|
||||
id: objectLists
|
||||
onChanged: drawCanvas.requestPaint()
|
||||
}
|
||||
|
||||
Settings {
|
||||
id: settings
|
||||
canvas: drawCanvas
|
||||
onChanged: drawCanvas.requestPaint()
|
||||
}
|
||||
|
||||
HistoryBrowser {
|
||||
id: historyBrowser
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LogGraphCanvas {
|
||||
id: drawCanvas
|
||||
anchors.top: parent.top
|
||||
anchors.left: sidebar.inPortrait ? parent.left : sidebar.right
|
||||
height: parent.height
|
||||
width: sidebar.inPortrait ? parent.width : parent.width - sidebar.width//*sidebar.position
|
||||
x: sidebar.width//*sidebar.position
|
||||
|
||||
xmin: settings.xmin
|
||||
ymax: settings.ymax
|
||||
xzoom: settings.xzoom
|
||||
yzoom: settings.yzoom
|
||||
xlabel: settings.xlabel
|
||||
ylabel: settings.ylabel
|
||||
yaxisstep: settings.yaxisstep
|
||||
xaxisstep: settings.xaxisstep
|
||||
logscalex: settings.logscalex
|
||||
linewidth: settings.linewidth
|
||||
textsize: settings.textsize
|
||||
showxgrad: settings.showxgrad
|
||||
showygrad: settings.showygrad
|
||||
|
||||
property bool firstDrawDone: false
|
||||
|
||||
onPainted: if(!firstDrawDone) {
|
||||
firstDrawDone = true;
|
||||
console.info("First paint done in " + (new Date().getTime()-(StartTime*1000)) + "ms")
|
||||
if(TestBuild == true) {
|
||||
console.log("Plot drawn in canvas, terminating test of build in 100ms.")
|
||||
testBuildTimer.start()
|
||||
}
|
||||
}
|
||||
|
||||
ViewPositionChangeOverlay {
|
||||
id: viewPositionChanger
|
||||
anchors.fill: parent
|
||||
canvas: parent
|
||||
settingsInstance: settings
|
||||
}
|
||||
|
||||
PickLocationOverlay {
|
||||
id: positionPicker
|
||||
anchors.fill: parent
|
||||
canvas: parent
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod void LogarithmPlotter::saveDiagram(string filename)
|
||||
Saves the diagram to a certain \c filename.
|
||||
*/
|
||||
function saveDiagram(filename) {
|
||||
if(['lpf'].indexOf(filename.split('.')[filename.split('.').length-1]) == -1)
|
||||
filename += '.lpf'
|
||||
settings.saveFilename = filename
|
||||
var objs = {}
|
||||
for(var objType in Objects.currentObjects){
|
||||
objs[objType] = []
|
||||
for(var obj of Objects.currentObjects[objType]) {
|
||||
objs[objType].push(obj.export())
|
||||
}
|
||||
}
|
||||
Helper.write(filename, JSON.stringify({
|
||||
"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,
|
||||
"history": history.serialize(),
|
||||
"width": root.width,
|
||||
"height": root.height,
|
||||
"objects": objs,
|
||||
"type": "logplotv1"
|
||||
}))
|
||||
alert.show(qsTr("Saved plot to '%1'.").arg(filename.split("/").pop()))
|
||||
history.saved = true
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod void LogarithmPlotter::saveDiagram(string filename)
|
||||
Loads the diagram from a certain \c filename.
|
||||
*/
|
||||
function loadDiagram(filename) {
|
||||
let basename = filename.split("/").pop()
|
||||
alert.show(qsTr("Loading file '%1'.").arg(basename))
|
||||
let data = JSON.parse(Helper.load(filename))
|
||||
let error = "";
|
||||
if(Object.keys(data).includes("type") && data["type"] == "logplotv1") {
|
||||
history.clear()
|
||||
// Importing settings
|
||||
settings.saveFilename = filename
|
||||
settings.xzoom = data["xzoom"]
|
||||
settings.yzoom = data["yzoom"]
|
||||
settings.xmin = data["xmin"]
|
||||
settings.ymax = data["ymax"]
|
||||
settings.xaxisstep = data["xaxisstep"]
|
||||
settings.yaxisstep = data["yaxisstep"]
|
||||
settings.xlabel = data["xaxislabel"]
|
||||
settings.ylabel = data["yaxislabel"]
|
||||
settings.logscalex = data["logscalex"]
|
||||
if("showxgrad" in data)
|
||||
settings.showxgrad = data["showxgrad"]
|
||||
if("showygrad" in data)
|
||||
settings.textsize = data["showygrad"]
|
||||
if("linewidth" in data)
|
||||
settings.linewidth = data["linewidth"]
|
||||
if("textsize" in data)
|
||||
settings.textsize = data["textsize"]
|
||||
root.height = data["height"]
|
||||
root.width = data["width"]
|
||||
|
||||
// Importing objects
|
||||
Objects.currentObjects = {}
|
||||
Object.keys(Objects.currentObjectsByName).forEach(key => {
|
||||
delete Objects.currentObjectsByName[key];
|
||||
// Required to keep the same reference for the copy of the object used in expression variable detection.
|
||||
// Another way would be to change the reference as well, but I feel like the code would be less clean.
|
||||
})
|
||||
for(let objType in data['objects']) {
|
||||
if(Object.keys(Objects.types).indexOf(objType) > -1) {
|
||||
Objects.currentObjects[objType] = []
|
||||
for(let objData of data['objects'][objType]) {
|
||||
let obj = new Objects.types[objType](...objData)
|
||||
Objects.currentObjects[objType].push(obj)
|
||||
Objects.currentObjectsByName[obj.name] = obj
|
||||
}
|
||||
} else {
|
||||
error += qsTr("Unknown object type: %1.").arg(objType) + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Updating object dependencies.
|
||||
for(let objName in Objects.currentObjectsByName)
|
||||
Objects.currentObjectsByName[objName].update()
|
||||
|
||||
// Importing history
|
||||
if("history" in data)
|
||||
history.unserialize(...data["history"])
|
||||
|
||||
// Refreshing sidebar
|
||||
if(sidebarSelector.currentIndex == 0) {
|
||||
// For some reason, if we load a file while the tab is on object,
|
||||
// we get stuck in a Qt-side loop? Qt bug or side-effect here, I don't know.
|
||||
sidebarSelector.currentIndex = 1
|
||||
objectLists.update()
|
||||
delayRefreshTimer.start()
|
||||
} else {
|
||||
objectLists.update()
|
||||
}
|
||||
} else {
|
||||
error = qsTr("Invalid file provided.")
|
||||
}
|
||||
if(error != "") {
|
||||
console.log(error)
|
||||
alert.show(qsTr("Could not save file: ") + error)
|
||||
// TODO: Error handling
|
||||
return
|
||||
}
|
||||
drawCanvas.requestPaint()
|
||||
alert.show(qsTr("Loaded file '%1'.").arg(basename))
|
||||
history.saved = true
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: delayRefreshTimer
|
||||
repeat: false
|
||||
interval: 1
|
||||
onTriggered: sidebarSelector.currentIndex = 0
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: testBuildTimer
|
||||
repeat: false
|
||||
interval: 100
|
||||
onTriggered: Qt.quit() // Quit after paint on test build
|
||||
}
|
||||
|
||||
onClosing: function(close) {
|
||||
if(!history.saved) {
|
||||
close.accepted = false
|
||||
appMenu.openSaveUnsavedChangesDialog()
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod void LogarithmPlotter::copyDiagramToClipboard()
|
||||
Copies the current diagram image to the clipboard.
|
||||
*/
|
||||
function copyDiagramToClipboard() {
|
||||
var file = Helper.gettmpfile()
|
||||
drawCanvas.save(file)
|
||||
Helper.copyImageToClipboard()
|
||||
alert.show(qsTr("Copied plot screenshot to clipboard!"))
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod void LogarithmPlotter::showAlert(string alertText)
|
||||
Shows an alert on the diagram.
|
||||
*/
|
||||
function showAlert(alertText) {
|
||||
// This function is called from the backend and is used to show alerts from there.
|
||||
alert.show(alertText)
|
||||
}
|
||||
|
||||
|
||||
Menu {
|
||||
id: updateMenu
|
||||
title: qsTr("&Update")
|
||||
Action {
|
||||
text: qsTr("&Update LogarithmPlotter")
|
||||
icon.name: 'update'
|
||||
onTriggered: Qt.openUrlExternally("https://dev.apps.ad5001.eu/logarithmplotter")
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlmethod void LogarithmPlotter::showUpdateMenu()
|
||||
Shows the update menu in the AppMenuBar.
|
||||
*/
|
||||
function showUpdateMenu() {
|
||||
appMenu.addMenu(updateMenu)
|
||||
}
|
||||
}
|
|
@ -1,236 +0,0 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import "../js/math/latex.js" as Latex
|
||||
|
||||
/*!
|
||||
\qmltype GreetScreen
|
||||
\inqmlmodule eu.ad5001.LogarithmPlotter.Popup
|
||||
\brief Overlay displayed when LogarithmPlotter is launched for the first time or when it was just updated.
|
||||
|
||||
It contains several settings as well as an easy access to the changelog
|
||||
|
||||
\sa LogarithmPlotter, Settings, AppMenuBar, Changelog
|
||||
*/
|
||||
Popup {
|
||||
id: greetingPopup
|
||||
x: (parent.width-width)/2
|
||||
y: Math.max(20, (parent.height-height)/2)
|
||||
width: Math.max(welcome.width+70, checkForUpdatesSetting.width, resetRedoStackSetting.width)+20
|
||||
height: Math.min(parent.height-40, 700)
|
||||
modal: true
|
||||
focus: true
|
||||
clip: true
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
|
||||
ScrollView {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: bottomButtons.height + 20
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
width: greetingPopup.width - 25
|
||||
spacing: 10
|
||||
clip: true
|
||||
topPadding: 35
|
||||
|
||||
Row {
|
||||
id: welcome
|
||||
height: logo.height
|
||||
spacing: 10
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
Image {
|
||||
id: logo
|
||||
source: "../icons/logarithmplotter.svg"
|
||||
sourceSize.width: 48
|
||||
sourceSize.height: 48
|
||||
width: 48
|
||||
height: 48
|
||||
}
|
||||
|
||||
Label {
|
||||
id: welcomeText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
wrapMode: Text.WordWrap
|
||||
font.pixelSize: 32
|
||||
text: qsTr("Welcome to LogarithmPlotter")
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
id: versionText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
wrapMode: Text.WordWrap
|
||||
width: implicitWidth
|
||||
font.pixelSize: 18
|
||||
font.italic: true
|
||||
text: qsTr("Version %1").arg(Helper.getVersion())
|
||||
}
|
||||
|
||||
Label {
|
||||
id: helpText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
wrapMode: Text.WordWrap
|
||||
font.pixelSize: 14
|
||||
width: parent.width - 50
|
||||
text: qsTr("Take a few seconds to configure LogarithmPlotter.\nThese settings can be changed at any time from the \"Settings\" menu.")
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: checkForUpdatesSetting
|
||||
anchors.left: parent.left
|
||||
checked: Helper.getSettingBool("check_for_updates")
|
||||
text: qsTr('Check for updates on startup (requires online connectivity)')
|
||||
onClicked: {
|
||||
Helper.setSettingBool("check_for_updates", checked)
|
||||
// Set in the menu bar
|
||||
appMenu.settingsMenu.children[0].checked = checked
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: resetRedoStackSetting
|
||||
anchors.left: parent.left
|
||||
checked: Helper.getSettingBool("reset_redo_stack")
|
||||
text: qsTr('Reset redo stack when a new action is added to history')
|
||||
onClicked: {
|
||||
Helper.setSettingBool("reset_redo_stack", checked)
|
||||
appMenu.settingsMenu.children[1].checked = checked
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: enableLatexSetting
|
||||
anchors.left: parent.left
|
||||
checked: Helper.getSettingBool("enable_latex")
|
||||
text: qsTr('Enable LaTeX rendering')
|
||||
onClicked: {
|
||||
Helper.setSettingBool("enable_latex", checked)
|
||||
appMenu.settingsMenu.children[2].checked = checked
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: autocloseFormulaSetting
|
||||
anchors.left: parent.left
|
||||
checked: Helper.getSettingBool("expression_editor.autoclose")
|
||||
text: qsTr('Automatically close parenthesises and brackets in expressions')
|
||||
onClicked: {
|
||||
Helper.setSettingBool("expression_editor.autoclose", checked)
|
||||
appMenu.settingsMenu.children[3].children[0].checked = checked
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: colorizeFormulaSetting
|
||||
anchors.left: parent.left
|
||||
checked: Helper.getSettingBool("expression_editor.colorize")
|
||||
text: qsTr('Enable syntax highlighting for expressions')
|
||||
onClicked: {
|
||||
Helper.setSettingBool("expression_editor.colorize", checked)
|
||||
appMenu.settingsMenu.children[3].children[1].checked = checked
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: autocompleteFormulaSetting
|
||||
anchors.left: parent.left
|
||||
checked: Helper.getSettingBool("autocompletion.enabled")
|
||||
text: qsTr('Enable autocompletion interface in expression editor')
|
||||
onClicked: {
|
||||
Helper.setSettingBool("autocompletion.enabled", checked)
|
||||
appMenu.settingsMenu.children[3].children[2].checked = checked
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
spacing: 10
|
||||
|
||||
Label {
|
||||
id: colorSchemeLabel
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
wrapMode: Text.WordWrap
|
||||
text: qsTr("Color scheme:")
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
model: ["Breeze Light", "Breeze Dark", "Solarized", "Github Light", "Github Dark", "Nord", "Monokai"]
|
||||
currentIndex: Helper.getSettingInt("expression_editor.color_scheme")
|
||||
|
||||
onActivated: function(index) {
|
||||
Helper.setSettingInt("expression_editor.color_scheme", index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: bottomSeparator
|
||||
opacity: 0.3
|
||||
color: sysPalette.windowText
|
||||
width: parent.width * 2 / 3
|
||||
height: 1
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: bottomButtons.top
|
||||
anchors.bottomMargin: 9
|
||||
}
|
||||
|
||||
Row {
|
||||
id: bottomButtons
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 7
|
||||
spacing: 10
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
Button {
|
||||
id: userManualBtn
|
||||
text: qsTr("User manual")
|
||||
font.pixelSize: 18
|
||||
onClicked: Qt.openUrlExternally("https://git.ad5001.eu/Ad5001/LogarithmPlotter/wiki/_Sidebar")
|
||||
}
|
||||
|
||||
Button {
|
||||
id: changelogBtn
|
||||
text: qsTr("Changelog")
|
||||
font.pixelSize: 18
|
||||
onClicked: changelog.open()
|
||||
}
|
||||
|
||||
Button {
|
||||
id: doneBtn
|
||||
text: qsTr("Done")
|
||||
font.pixelSize: 18
|
||||
onClicked: greetingPopup.close()
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: if(Helper.getSetting("last_install_greet") != Helper.getVersion()) {
|
||||
greetingPopup.open()
|
||||
}
|
||||
|
||||
onClosed: Helper.setSetting("last_install_greet", Helper.getVersion())
|
||||
}
|
|
@ -1,334 +0,0 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Dialogs
|
||||
import QtQuick.Controls
|
||||
|
||||
/*!
|
||||
\qmltype ThanksTo
|
||||
\inqmlmodule eu.ad5001.LogarithmPlotter.Popup
|
||||
\brief Thanks to popup of LogarithmPlotter.
|
||||
|
||||
\sa LogarithmPlotter
|
||||
*/
|
||||
BaseDialog {
|
||||
id: about
|
||||
title: qsTr("Thanks and Contributions - LogarithmPlotter")
|
||||
width: 450
|
||||
minimumHeight: 710
|
||||
|
||||
Column {
|
||||
anchors {
|
||||
top: parent.top;
|
||||
left: parent.left;
|
||||
bottom: parent.bottom;
|
||||
right: parent.right;
|
||||
topMargin: margin;
|
||||
leftMargin: margin;
|
||||
bottomMargin: margin;
|
||||
rightMargin: margin;
|
||||
}
|
||||
|
||||
spacing: 10
|
||||
|
||||
ListView {
|
||||
id: librariesListView
|
||||
anchors.left: parent.left
|
||||
width: parent.width
|
||||
//height: parent.height
|
||||
implicitHeight: contentItem.childrenRect.height
|
||||
interactive: false
|
||||
|
||||
model: ListModel {
|
||||
Component.onCompleted: {
|
||||
append({
|
||||
libName: 'expr-eval',
|
||||
license: 'MIT',
|
||||
licenseLink: 'https://raw.githubusercontent.com/silentmatt/expr-eval/master/LICENSE.txt',
|
||||
linkName: qsTr('Source code'),
|
||||
link: 'https://github.com/silentmatt/expr-eval',
|
||||
authors: [{
|
||||
authorLine: qsTr('Original library by Raphael Graf'),
|
||||
email: 'r@undefined.ch',
|
||||
website: 'https://web.archive.org/web/20111023001618/http://www.undefined.ch/mparser/index.html',
|
||||
websiteName: qsTr('Source')
|
||||
}, {
|
||||
authorLine: qsTr('Ported to Javascript by Matthew Crumley'),
|
||||
email: 'email@matthewcrumley.com',
|
||||
website: 'https://silentmatt.com/',
|
||||
websiteName: qsTr('Website')
|
||||
}, {
|
||||
authorLine: qsTr('Ported to QMLJS by Ad5001'),
|
||||
email: 'mail@ad5001.eu',
|
||||
website: 'https://ad5001.eu/',
|
||||
websiteName: qsTr('Website')
|
||||
}]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
header: Label {
|
||||
id: librariesUsedHeader
|
||||
wrapMode: Text.WordWrap
|
||||
font.pixelSize: 25
|
||||
text: qsTr("Libraries included")
|
||||
height: implicitHeight + 10
|
||||
}
|
||||
|
||||
delegate: Column {
|
||||
id: libClmn
|
||||
width: librariesListView.width
|
||||
spacing: 10
|
||||
|
||||
Item {
|
||||
height: libraryHeader.height
|
||||
width: parent.width
|
||||
|
||||
Label {
|
||||
id: libraryHeader
|
||||
anchors.left: parent.left
|
||||
wrapMode: Text.WordWrap
|
||||
font.pixelSize: 18
|
||||
text: libName
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.right: parent.right
|
||||
height: parent.height
|
||||
spacing: 10
|
||||
|
||||
Button {
|
||||
height: parent.height
|
||||
text: license
|
||||
icon.name: 'license'
|
||||
onClicked: Qt.openUrlExternally(licenseLink)
|
||||
}
|
||||
|
||||
Button {
|
||||
height: parent.height
|
||||
text: linkName
|
||||
icon.name: 'web-browser'
|
||||
onClicked: Qt.openUrlExternally(link)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: libAuthors
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
model: authors
|
||||
width: parent.width - 10
|
||||
implicitHeight: contentItem.childrenRect.height
|
||||
interactive: false
|
||||
|
||||
delegate: Item {
|
||||
id: libAuthor
|
||||
width: librariesListView.width - 10
|
||||
height: 50
|
||||
|
||||
Label {
|
||||
id: libAuthorName
|
||||
anchors.left: parent.left
|
||||
anchors.right: buttons.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
wrapMode: Text.WordWrap
|
||||
font.pixelSize: 14
|
||||
text: authorLine
|
||||
}
|
||||
|
||||
Row {
|
||||
id: buttons
|
||||
anchors.right: parent.right
|
||||
height: parent.height
|
||||
spacing: 10
|
||||
|
||||
Button {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: websiteName
|
||||
icon.name: 'web-browser'
|
||||
height: parent.height - 10
|
||||
onClicked: Qt.openUrlExternally(website)
|
||||
}
|
||||
|
||||
Button {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: qsTr('Email')
|
||||
icon.name: 'email'
|
||||
height: parent.height - 10
|
||||
onClicked: Qt.openUrlExternally('mailto:' + email)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: libSeparator
|
||||
opacity: 0.3
|
||||
color: sysPalette.windowText
|
||||
width: parent.width
|
||||
height: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: translationsListView
|
||||
anchors.left: parent.left
|
||||
width: parent.width
|
||||
implicitHeight: contentItem.childrenRect.height
|
||||
interactive: false
|
||||
spacing: 3
|
||||
|
||||
|
||||
model: ListModel {
|
||||
Component.onCompleted: {
|
||||
append({
|
||||
tranName: '🇬🇧 ' + qsTr('English'),
|
||||
link: 'https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/en/',
|
||||
authors: [{
|
||||
authorLine: 'Ad5001',
|
||||
email: 'mail@ad5001.eu',
|
||||
website: 'https://ad5001.eu',
|
||||
websiteName: qsTr('Website')
|
||||
}]
|
||||
})
|
||||
append({
|
||||
tranName: '🇫🇷 ' + qsTr('French'),
|
||||
link: 'https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/fr/',
|
||||
authors: [{
|
||||
authorLine: 'Ad5001',
|
||||
website: 'https://ad5001.eu',
|
||||
websiteName: qsTr('Website')
|
||||
}]
|
||||
})
|
||||
append({
|
||||
tranName: '🇩🇪 ' + qsTr('German'),
|
||||
link: 'https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/de/',
|
||||
authors: [{
|
||||
authorLine: 'Ad5001',
|
||||
website: 'https://ad5001.eu',
|
||||
websiteName: qsTr('Website')
|
||||
}]
|
||||
})
|
||||
append({
|
||||
tranName: '🇭🇺 ' + qsTr('Hungarian'),
|
||||
link: 'https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/hu/',
|
||||
authors: [{
|
||||
authorLine: 'Óvári',
|
||||
website: 'https://github.com/ovari',
|
||||
websiteName: qsTr('Github')
|
||||
}]
|
||||
})
|
||||
append({
|
||||
tranName: '🇳🇴 ' + qsTr('Norwegian'),
|
||||
link: 'https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/no/',
|
||||
authors: [{
|
||||
authorLine: 'Allan Nordhøy',
|
||||
website: 'https://github.com/comradekingu',
|
||||
websiteName: qsTr('Github')
|
||||
}]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
header: Label {
|
||||
id: translationsHeader
|
||||
wrapMode: Text.WordWrap
|
||||
font.pixelSize: 25
|
||||
text: qsTr("Translations included")
|
||||
height: implicitHeight + 10
|
||||
}
|
||||
|
||||
delegate: Column {
|
||||
id: tranClmn
|
||||
width: translationsListView.width
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: translationHeader.height + 10
|
||||
|
||||
Label {
|
||||
id: translationHeader
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
wrapMode: Text.WordWrap
|
||||
font.pixelSize: 18
|
||||
text: tranName
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: 30
|
||||
spacing: 10
|
||||
|
||||
Button {
|
||||
height: parent.height
|
||||
text: qsTr('Improve')
|
||||
icon.name: 'web-browser'
|
||||
onClicked: Qt.openUrlExternally(link)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: tranAuthors
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
model: authors
|
||||
width: parent.width - 10
|
||||
implicitHeight: contentItem.childrenRect.height
|
||||
interactive: false
|
||||
|
||||
delegate: Item {
|
||||
id: tranAuthor
|
||||
width: tranAuthors.width
|
||||
height: 40
|
||||
|
||||
Label {
|
||||
id: tranAuthorName
|
||||
anchors.left: parent.left
|
||||
anchors.right: buttons.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
wrapMode: Text.WordWrap
|
||||
font.pixelSize: 14
|
||||
text: authorLine
|
||||
}
|
||||
|
||||
Row {
|
||||
id: buttons
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: 30
|
||||
spacing: 10
|
||||
|
||||
Button {
|
||||
text: websiteName
|
||||
icon.name: 'web-browser'
|
||||
height: parent.height
|
||||
onClicked: Qt.openUrlExternally(website)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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/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
|
|
@ -1 +0,0 @@
|
|||
../../common/angle.svg
|
|
@ -1,95 +0,0 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.pragma library
|
||||
|
||||
.import "../objects.js" as Objects
|
||||
.import "../mathlib.js" as MathLib
|
||||
.import "../math/latex.js" as Latex
|
||||
.import "../utils.js" as Utils
|
||||
.import "../objs/common.js" as Common
|
||||
.import "common.js" as C
|
||||
|
||||
class EditedPosition extends C.Action {
|
||||
// Action used for objects that have a X and Y expression properties (points, texts...)
|
||||
type(){return 'EditedPosition'}
|
||||
|
||||
icon(){return 'position'}
|
||||
|
||||
color(darkVer=false){
|
||||
return darkVer ? 'seagreen' : 'lightseagreen';
|
||||
}
|
||||
|
||||
constructor(targetName = "", targetType = "Point", previousXValue = "", newXValue = "", previousYValue = "", newYValue = "") {
|
||||
super(targetName, targetType)
|
||||
let imports = {
|
||||
'previousXValue': previousXValue,
|
||||
'previousYValue': previousYValue,
|
||||
'newXValue': newXValue,
|
||||
'newYValue': newYValue
|
||||
}
|
||||
for(let name in imports)
|
||||
this[name] = (typeof imports[name]) == 'string' ? new MathLib.Expression(imports[name]) : imports[name]
|
||||
this.setReadableValues()
|
||||
}
|
||||
|
||||
undo() {
|
||||
Objects.currentObjectsByName[this.targetName].x = this.previousXValue
|
||||
Objects.currentObjectsByName[this.targetName].y = this.previousYValue
|
||||
Objects.currentObjectsByName[this.targetName].update()
|
||||
}
|
||||
|
||||
redo() {
|
||||
Objects.currentObjectsByName[this.targetName].x = this.newXValue
|
||||
Objects.currentObjectsByName[this.targetName].y = this.newYValue
|
||||
Objects.currentObjectsByName[this.targetName].update()
|
||||
}
|
||||
|
||||
setReadableValues() {
|
||||
this.prevString = `(${this.previousXValue.toString()},${this.previousYValue.toString()})`
|
||||
this.nextString = `(${this.newXValue.toString()},${this.newYValue.toString()})`
|
||||
// Render as LaTeX
|
||||
if(Latex.enabled) {
|
||||
this.prevHTML = this.renderLatexAsHtml(`\\left(${this.previousXValue.latexMarkup},${this.previousYValue.latexMarkup}\\right)`)
|
||||
this.nextHTML = this.renderLatexAsHtml(`\\left(${this.newXValue.latexMarkup},${this.newYValue.latexMarkup}\\right)`)
|
||||
} else {
|
||||
this.prevHTML = '<tt style="background: rgba(128,128,128,0.1);"> '+Utils.escapeHTML(this.prevString)+' </tt>'
|
||||
this.nextHTML = '<tt style="background: rgba(128,128,128,0.1);"> '+Utils.escapeHTML(this.nextString)+' </tt>'
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export() {
|
||||
return [this.targetName, this.targetType,
|
||||
this.previousXValue.toEditableString(), this.newXValue.toEditableString(),
|
||||
this.previousYValue.toEditableString(), this.newYValue.toEditableString()]
|
||||
}
|
||||
|
||||
getReadableString() {
|
||||
return qsTr('Position of %1 %2 set from "%3" to "%4".')
|
||||
.arg(Objects.types[this.targetType].displayType())
|
||||
.arg(this.targetName).arg(this.prevString).arg(this.nextString)
|
||||
}
|
||||
|
||||
getHTMLString() {
|
||||
return qsTr('Position of %1 set from %2 to %3.')
|
||||
.arg('<b style="font-size: 15px;"> ' + this.targetName + ' </b>')
|
||||
.arg(this.prevHTML)
|
||||
.arg(this.nextHTML)
|
||||
}
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.pragma library
|
||||
|
||||
.import "../expr-eval.js" as ExprEval
|
||||
.import "../utils.js" as Utils
|
||||
.import "latex.js" as Latex
|
||||
|
||||
var evalVariables = { // Variables not provided by expr-eval.js, needs to be provided manually
|
||||
"pi": Math.PI,
|
||||
"PI": Math.PI,
|
||||
"π": Math.PI,
|
||||
"inf": Infinity,
|
||||
"infinity": Infinity,
|
||||
"Infinity": Infinity,
|
||||
"∞": Infinity,
|
||||
"e": Math.E,
|
||||
"E": Math.E,
|
||||
"true": true,
|
||||
"false": false
|
||||
|
||||
}
|
||||
|
||||
var currentVars = {}
|
||||
var currentObjectsByName = {} // Mirror of currentObjectsByName in objects.js
|
||||
|
||||
const parser = new ExprEval.Parser()
|
||||
|
||||
parser.consts = Object.assign({}, parser.consts, evalVariables)
|
||||
|
||||
/**
|
||||
* Parses arguments for a function, returns the corresponding JS function if it exists.
|
||||
* Throws either usage error otherwise.
|
||||
* @param {array} args - Arguments of the function, either [ ExecutableObject ] or [ string, variable ].
|
||||
* @param {string} usage1 - Usage for executable object.
|
||||
* @param {string} usage2 - Usage for string function.
|
||||
* @return {callable} JS function to call..
|
||||
*/
|
||||
function parseArgumentsForFunction(args, usage1, usage2) {
|
||||
let f, target, variable
|
||||
if(args.length == 1) {
|
||||
// Parse object
|
||||
f = args[0]
|
||||
if(typeof f != 'object' || !f.execute)
|
||||
throw EvalError(qsTranslate('usage', 'Usage: %1').arg(usage1))
|
||||
let target = f
|
||||
f = (x) => target.execute(x)
|
||||
} else if(args.length == 2) {
|
||||
// Parse variable
|
||||
[f,variable] = args
|
||||
if(typeof f != 'string' || typeof variable != 'string')
|
||||
throw EvalError(qsTranslate('usage', 'Usage: %1').arg(usage2))
|
||||
f = parser.parse(f).toJSFunction(variable, currentVars)
|
||||
} else
|
||||
throw EvalError(qsTranslate('usage', 'Usage: %1 or\n%2').arg(usage1).arg(usage2))
|
||||
return f
|
||||
}
|
||||
|
||||
// Function definition
|
||||
parser.functions.integral = function(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 = parseArgumentsForFunction(args, usage1, usage2)
|
||||
if(a == null || b == null)
|
||||
throw EvalError(qsTranslate('usage', 'Usage: %1 or\n%2').arg(usage1).arg(usage2))
|
||||
|
||||
// https://en.wikipedia.org/wiki/Simpson%27s_rule
|
||||
// Simpler, faster than tokenizing the expression
|
||||
return (b-a)/6*(f(a)+4*f((a+b)/2)+f(b))
|
||||
}
|
||||
|
||||
parser.functions.derivative = function(...args) {
|
||||
let usage1 = qsTranslate('usage', 'derivative(<f: ExecutableObject>, <x: number>)')
|
||||
let usage2 = qsTranslate('usage', 'derivative(<f: string>, <variable: string>, <x: number>)')
|
||||
let x = args.pop()
|
||||
let f = parseArgumentsForFunction(args, usage1, usage2)
|
||||
if(x == null)
|
||||
throw EvalError(qsTranslate('usage', 'Usage: %1 or\n%2').arg(usage1).arg(usage2))
|
||||
|
||||
let derivative_precision = x/10
|
||||
return (f(x+derivative_precision/2)-f(x-derivative_precision/2))/derivative_precision
|
||||
}
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.pragma library
|
||||
|
||||
.import "common.js" as C
|
||||
.import "latex.js" as Latex
|
||||
.import "../utils.js" as Utils
|
||||
|
||||
/**
|
||||
* Represents any kind of x-based or non variable based expression.
|
||||
*/
|
||||
class Expression {
|
||||
constructor(expr) {
|
||||
this.expr = Utils.exponentsToExpression(expr)
|
||||
this.calc = C.parser.parse(this.expr).simplify()
|
||||
this.cached = this.isConstant()
|
||||
this.cachedValue = this.cached && this.allRequirementsFullfilled() ? this.calc.evaluate(C.currentObjectsByName) : null
|
||||
this.latexMarkup = Latex.expression(this.calc.tokens)
|
||||
}
|
||||
|
||||
isConstant() {
|
||||
let vars = this.calc.variables()
|
||||
return !vars.includes("x") && !vars.includes("n")
|
||||
}
|
||||
|
||||
requiredObjects() {
|
||||
return this.calc.variables().filter(objName => objName != "x" && objName != "n")
|
||||
}
|
||||
|
||||
allRequirementsFullfilled() {
|
||||
return this.requiredObjects().every(objName => objName in C.currentObjectsByName)
|
||||
}
|
||||
|
||||
undefinedVariables() {
|
||||
return this.requiredObjects().filter(objName => !(objName in C.currentObjectsByName))
|
||||
}
|
||||
|
||||
recache() {
|
||||
if(this.cached)
|
||||
this.cachedValue = this.calc.evaluate(C.currentObjectsByName)
|
||||
}
|
||||
|
||||
execute(x = 1) {
|
||||
if(this.cached) {
|
||||
if(this.cachedValue == null)
|
||||
this.cachedValue = this.calc.evaluate(C.currentObjectsByName)
|
||||
return this.cachedValue
|
||||
}
|
||||
C.currentVars = Object.assign({'x': x}, C.currentObjectsByName)
|
||||
return this.calc.evaluate(C.currentVars)
|
||||
}
|
||||
|
||||
simplify(x) {
|
||||
var expr = this.calc.substitute('x', x).simplify()
|
||||
if(expr.evaluate() == 0) return '0'
|
||||
var str = Utils.makeExpressionReadable(expr.toString());
|
||||
if(str != undefined && str.match(/^\d*\.\d+$/)) {
|
||||
if(str.split('.')[1].split('0').length > 7) {
|
||||
// Likely rounding error
|
||||
str = parseFloat(str.substring(0, str.length-1)).toString();
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
duplicate() {
|
||||
return new Expression(this.toEditableString())
|
||||
}
|
||||
|
||||
toEditableString() {
|
||||
return this.calc.toString()
|
||||
}
|
||||
|
||||
toString(forceSign=false) {
|
||||
let str = Utils.makeExpressionReadable(this.calc.toString())
|
||||
if(str[0] != '-' && forceSign) str = '+' + str
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
function executeExpression(expr){
|
||||
return (new Expression(expr.toString())).execute()
|
||||
}
|
|
@ -1,282 +0,0 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.pragma library
|
||||
|
||||
.import "../expr-eval.js" as ExprEval
|
||||
|
||||
|
||||
/**
|
||||
* true if latex has been enabled by the user, false otherwise.
|
||||
*/
|
||||
var enabled = false
|
||||
/**
|
||||
* LaTeX python backend QObject.
|
||||
*/
|
||||
var Renderer = null
|
||||
/**
|
||||
* Default window color used to render LaTeX formulas.
|
||||
*/
|
||||
var defaultColor = "black"
|
||||
|
||||
/**
|
||||
* Puts element within parenthesis.
|
||||
*
|
||||
* @param {string} elem - element to put within parenthesis.
|
||||
* @returns {string}
|
||||
*/
|
||||
function par(elem) {
|
||||
return '(' + elem + ')'
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the element contains at least one of the elements of
|
||||
* the string array contents, but not at the first position of the string,
|
||||
* and returns the parenthesis version if so.
|
||||
*
|
||||
* @param {string} elem - element to put within parenthesis.
|
||||
* @param {Array} contents - Array of elements to put within parenthesis.
|
||||
* @returns {string}
|
||||
*/
|
||||
function parif(elem, contents) {
|
||||
elem = elem.toString()
|
||||
if(elem[0] != "(" && elem[elem.length-1] != ")" && contents.some(x => elem.indexOf(x) > 0))
|
||||
return par(elem)
|
||||
if(elem[0] == "(" && elem[elem.length-1] == ")")
|
||||
return elem.substr(1, elem.length-2)
|
||||
return elem
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a latex expression for a function.
|
||||
*
|
||||
* @param {string} f - Function to convert
|
||||
* @param {Array} args - Arguments of the function
|
||||
* @returns {string}
|
||||
*/
|
||||
function functionToLatex(f, args) {
|
||||
switch(f) {
|
||||
case "derivative":
|
||||
if(args.length == 3)
|
||||
return '\\frac{d' + args[0].substr(1, args[0].length-2).replace(new RegExp(args[1].substr(1, args[1].length-2), 'g'), 'x') + '}{dx}';
|
||||
else
|
||||
return '\\frac{d' + args[0] + '}{dx}(x)';
|
||||
break;
|
||||
case "integral":
|
||||
if(args.length == 4)
|
||||
return '\\int\\limits_{' + args[0] + '}^{' + args[1] + '}' + args[2].substr(1, args[2].length-2) + ' d' + args[3].substr(1, args[3].length-2);
|
||||
else
|
||||
return '\\int\\limits_{' + args[0] + '}^{' + args[1] + '}' + args[2] + '(t) dt';
|
||||
break;
|
||||
case "sqrt":
|
||||
return '\\sqrt\\left(' + args.join(', ') + '\\right)';
|
||||
break;
|
||||
case "abs":
|
||||
return '\\left|' + args.join(', ') + '\\right|';
|
||||
break;
|
||||
case "floor":
|
||||
return '\\left\\lfloor' + args.join(', ') + '\\right\\rfloor';
|
||||
break;
|
||||
case "ceil":
|
||||
return '\\left\\lceil' + args.join(', ') + '\\right\\rceil';
|
||||
break;
|
||||
default:
|
||||
return '\\mathrm{' + f + '}\\left(' + args.join(', ') + '\\right)';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a latex variable from a variable.
|
||||
*
|
||||
* @param {string} vari - variable text to convert
|
||||
* @param {bool} wrapIn$ - checks whether the escaped chars should be escaped
|
||||
* @returns {string}
|
||||
*/
|
||||
function variable(vari, wrapIn$ = false) {
|
||||
let unicodechars = ["α","β","γ","δ","ε","ζ","η",
|
||||
"π","θ","κ","λ","μ","ξ","ρ",
|
||||
"ς","σ","τ","φ","χ","ψ","ω",
|
||||
"Γ","Δ","Θ","Λ","Ξ","Π","Σ",
|
||||
"Φ","Ψ","Ω","ₐ","ₑ","ₒ","ₓ",
|
||||
"ₕ","ₖ","ₗ","ₘ","ₙ","ₚ","ₛ",
|
||||
"ₜ","¹","²","³","⁴","⁵","⁶",
|
||||
"⁷","⁸","⁹","⁰","₁","₂","₃",
|
||||
"₄","₅","₆","₇","₈","₉","₀",
|
||||
"pi", "∞"]
|
||||
let equivalchars = ["\\alpha","\\beta","\\gamma","\\delta","\\epsilon","\\zeta","\\eta",
|
||||
"\\pi","\\theta","\\kappa","\\lambda","\\mu","\\xi","\\rho",
|
||||
"\\sigma","\\sigma","\\tau","\\phi","\\chi","\\psi","\\omega",
|
||||
"\\Gamma","\\Delta","\\Theta","\\Lambda","\\Xi","\\Pi","\\Sigma",
|
||||
"\\Phy","\\Psi","\\Omega","{}_{a}","{}_{e}","{}_{o}","{}_{x}",
|
||||
"{}_{h}","{}_{k}","{}_{l}","{}_{m}","{}_{n}","{}_{p}","{}_{s}",
|
||||
"{}_{t}","{}^{1}","{}^{2}","{}^{3}","{}^{4}","{}^{5}","{}^{6}",
|
||||
"{}^{7}","{}^{8}","{}^{9}","{}^{0}","{}_{1}","{}_{2}","{}_{3}",
|
||||
"{}_{4}","{}_{5}","{}_{6}","{}_{7}","{}_{8}","{}_{9}","{}_{0}",
|
||||
"\\pi", "\\infty"]
|
||||
if(wrapIn$)
|
||||
for(let i = 0; i < unicodechars.length; i++) {
|
||||
if(vari.includes(unicodechars[i]))
|
||||
vari = vari.replace(new RegExp(unicodechars[i], 'g'), '$'+equivalchars[i]+'$')
|
||||
}
|
||||
else
|
||||
for(let i = 0; i < unicodechars.length; i++) {
|
||||
if(vari.includes(unicodechars[i]))
|
||||
vari = vari.replace(new RegExp(unicodechars[i], 'g'), equivalchars[i])
|
||||
}
|
||||
return vari;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts expr-eval tokens to a latex string.
|
||||
*
|
||||
* @param {Array} tokens - expr-eval tokens list
|
||||
* @returns {string}
|
||||
*/
|
||||
function expression(tokens) {
|
||||
var nstack = [];
|
||||
var n1, n2, n3;
|
||||
var f, args, argCount;
|
||||
for (var i = 0; i < tokens.length; i++) {
|
||||
var item = tokens[i];
|
||||
var type = item.type;
|
||||
|
||||
switch(type) {
|
||||
case ExprEval.INUMBER:
|
||||
if(item.value == Infinity) {
|
||||
nstack.push("\\infty")
|
||||
} else if(typeof item.value === 'number' && item.value < 0) {
|
||||
nstack.push(par(item.value));
|
||||
} else if(Array.isArray(item.value)) {
|
||||
nstack.push('[' + item.value.map(ExprEval.escapeValue).join(', ') + ']');
|
||||
} else {
|
||||
nstack.push(ExprEval.escapeValue(item.value));
|
||||
}
|
||||
break;
|
||||
case ExprEval.IOP2:
|
||||
n2 = nstack.pop();
|
||||
n1 = nstack.pop();
|
||||
f = item.value;
|
||||
switch(f) {
|
||||
case '-':
|
||||
case '+':
|
||||
nstack.push(n1 + f + n2);
|
||||
break;
|
||||
case '||':
|
||||
case 'or':
|
||||
case '&&':
|
||||
case 'and':
|
||||
case '==':
|
||||
case '!=':
|
||||
nstack.push(par(n1) + f + par(n2));
|
||||
break;
|
||||
case '*':
|
||||
if(n2 == "\\pi" || n2 == "e" || n2 == "x" || n2 == "n")
|
||||
nstack.push(parif(n1,['+','-']) + n2)
|
||||
else
|
||||
nstack.push(parif(n1,['+','-']) + " \\times " + parif(n2,['+','-']));
|
||||
break;
|
||||
case '/':
|
||||
nstack.push("\\frac{" + n1 + "}{" + n2 + "}");
|
||||
break;
|
||||
case '^':
|
||||
nstack.push(parif(n1,['+','-','*','/','!']) + "^{" + n2 + "}");
|
||||
break;
|
||||
case '%':
|
||||
nstack.push(parif(n1,['+','-','*','/','!','^']) + " \\mathrm{mod} " + parif(n2,['+','-','*','/','!','^']));
|
||||
break;
|
||||
case '[':
|
||||
nstack.push(n1 + '[' + n2 + ']');
|
||||
break;
|
||||
default:
|
||||
throw new EvalError("Unknown operator " + ope + ".");
|
||||
}
|
||||
break;
|
||||
case ExprEval.IOP3: // Thirdiary operator
|
||||
n3 = nstack.pop();
|
||||
n2 = nstack.pop();
|
||||
n1 = nstack.pop();
|
||||
f = item.value;
|
||||
if (f === '?') {
|
||||
nstack.push('(' + n1 + ' ? ' + n2 + ' : ' + n3 + ')');
|
||||
} else {
|
||||
throw new EvalError('Unknown operator ' + ope + '.');
|
||||
}
|
||||
break;
|
||||
case ExprEval.IVAR:
|
||||
case ExprEval.IVARNAME:
|
||||
nstack.push(variable(item.value.toString()));
|
||||
break;
|
||||
case ExprEval.IOP1: // Unary operator
|
||||
n1 = nstack.pop();
|
||||
f = item.value;
|
||||
switch(f) {
|
||||
case '-':
|
||||
case '+':
|
||||
nstack.push(par(f + n1));
|
||||
break;
|
||||
case '!':
|
||||
nstack.push(parif(n1,['+','-','*','/','^']) + '!');
|
||||
break;
|
||||
default:
|
||||
nstack.push(f + parif(n1,['+','-','*','/','^']));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ExprEval.IFUNCALL:
|
||||
argCount = item.value;
|
||||
args = [];
|
||||
while (argCount-- > 0) {
|
||||
args.unshift(nstack.pop());
|
||||
}
|
||||
f = nstack.pop();
|
||||
// Handling various functions
|
||||
nstack.push(functionToLatex(f, args))
|
||||
break;
|
||||
case ExprEval.IFUNDEF:
|
||||
nstack.push(par(n1 + '(' + args.join(', ') + ') = ' + n2));
|
||||
break;
|
||||
case ExprEval.IMEMBER:
|
||||
n1 = nstack.pop();
|
||||
nstack.push(n1 + '.' + item.value);
|
||||
break;
|
||||
case ExprEval.IARRAY:
|
||||
argCount = item.value;
|
||||
args = [];
|
||||
while (argCount-- > 0) {
|
||||
args.unshift(nstack.pop());
|
||||
}
|
||||
nstack.push('[' + args.join(', ') + ']');
|
||||
break;
|
||||
case ExprEval.IEXPR:
|
||||
nstack.push('(' + expression(item.value) + ')');
|
||||
break;
|
||||
case ExprEval.IENDSTATEMENT:
|
||||
break;
|
||||
default:
|
||||
throw new EvalError('invalid Expression');
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (nstack.length > 1) {
|
||||
nstack = [ nstack.join(';') ]
|
||||
}
|
||||
return String(nstack[0]);
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.pragma library
|
||||
|
||||
.import "utils.js" as Utils
|
||||
.import "math/common.js" as MathCommons
|
||||
.import "parameters.js" as P
|
||||
|
||||
var types = {}
|
||||
|
||||
var currentObjects = {}
|
||||
var currentObjectsByName = {}
|
||||
MathCommons.currentObjectsByName = currentObjectsByName // Required for using objects in variables.
|
||||
|
||||
function renameObject(oldName, newName) {
|
||||
/**
|
||||
* Renames an object from its old name to the new one.
|
||||
* @param {string} oldName - Current name of the object.
|
||||
* @param {string} newName - Name to rename the object to.
|
||||
*/
|
||||
let obj = currentObjectsByName[oldName]
|
||||
delete currentObjectsByName[oldName]
|
||||
currentObjectsByName[newName] = obj
|
||||
obj.name = newName
|
||||
}
|
||||
|
||||
function deleteObject(objName) {
|
||||
/**
|
||||
* Deletes an object by its given name.
|
||||
* @param {string} objName - Current name of the object.
|
||||
*/
|
||||
let obj = currentObjectsByName[objName]
|
||||
currentObjects[obj.type].splice(currentObjects[obj.type].indexOf(obj),1)
|
||||
obj.delete()
|
||||
delete currentObjectsByName[objName]
|
||||
}
|
||||
|
||||
function getObjectsName(objType) {
|
||||
/**
|
||||
* Gets a list of all names of a certain object type.
|
||||
* @param {string} objType - Type of the object to query. Can be ExecutableObject for all ExecutableObjects.
|
||||
* @return {array} List of names of the objects.
|
||||
*/
|
||||
if(objType == "ExecutableObject") {
|
||||
// NOTE: QMLJS does not support flatMap.
|
||||
// return getExecutableTypes().flatMap(elemType => currentObjects[elemType].map(obj => obj.name))
|
||||
let types = getExecutableTypes()
|
||||
let elementNames = ['']
|
||||
for(let elemType of types)
|
||||
elementNames = elementNames.concat(currentObjects[elemType].map(obj => obj.name))
|
||||
return elementNames
|
||||
}
|
||||
if(currentObjects[objType] == undefined) return []
|
||||
return currentObjects[objType].map(obj => obj.name)
|
||||
}
|
||||
|
||||
function getExecutableTypes() {
|
||||
/**
|
||||
* Returns a list of all object types which are executable objects.
|
||||
* @return {array} List of all object types which are executable objects.
|
||||
*/
|
||||
return Object.keys(currentObjects).filter(objType => types[objType].executable())
|
||||
}
|
||||
|
||||
function createNewRegisteredObject(objType, args=[]) {
|
||||
/**
|
||||
* Creates and register an object in the database.
|
||||
* @param {string} objType - Type of the object to create.
|
||||
* @param {string} args - List of arguments for the objects (can be empty).
|
||||
* @return {[objType]} Newly created object.
|
||||
*/
|
||||
if(Object.keys(types).indexOf(objType) == -1) return null // Object type does not exist.
|
||||
var newobj = new types[objType](...args)
|
||||
if(Object.keys(currentObjects).indexOf(objType) == -1) {
|
||||
currentObjects[objType] = []
|
||||
}
|
||||
currentObjects[objType].push(newobj)
|
||||
currentObjectsByName[newobj.name] = newobj
|
||||
return newobj
|
||||
}
|
|
@ -1,146 +0,0 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.pragma library
|
||||
|
||||
.import "common.js" as Common
|
||||
.import "function.js" as F
|
||||
.import "../objects.js" as Objects
|
||||
.import "../utils.js" as Utils
|
||||
.import "../mathlib.js" as MathLib
|
||||
.import "../historylib.js" as HistoryLib
|
||||
.import "../parameters.js" as P
|
||||
.import "../math/latex.js" as Latex
|
||||
|
||||
|
||||
class GainBode extends Common.ExecutableObject {
|
||||
static type(){return 'Gain Bode'}
|
||||
static displayType(){return qsTr('Bode Magnitude')}
|
||||
static displayTypeMultiple(){return qsTr('Bode Magnitudes')}
|
||||
static properties() {return {
|
||||
[QT_TRANSLATE_NOOP('prop','om_0')]: new P.ObjectType('Point'),
|
||||
[QT_TRANSLATE_NOOP('prop','pass')]: P.Enum.BodePass,
|
||||
[QT_TRANSLATE_NOOP('prop','gain')]: new P.Expression(),
|
||||
[QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position,
|
||||
[QT_TRANSLATE_NOOP('prop','labelX')]: 'number',
|
||||
[QT_TRANSLATE_NOOP('prop','omGraduation')]: 'boolean'
|
||||
}}
|
||||
|
||||
constructor(name = null, visible = true, color = null, labelContent = 'name + value',
|
||||
om_0 = '', pass = 'high', gain = '20', labelPosition = 'above', labelX = 1, omGraduation = false) {
|
||||
if(name == null) name = Common.getNewName('G')
|
||||
if(name == 'G') name = 'G₀' // G is reserved for sum of BODE magnitudes (Somme gains Bode).
|
||||
super(name, visible, color, labelContent)
|
||||
if(typeof om_0 == "string") {
|
||||
// Point name or create one
|
||||
om_0 = Objects.currentObjectsByName[om_0]
|
||||
if(om_0 == null) {
|
||||
// Create new point
|
||||
om_0 = Objects.createNewRegisteredObject('Point', [Common.getNewName('ω'), true, this.color, 'name'])
|
||||
HistoryLib.history.addToHistory(new HistoryLib.CreateNewObject(om_0.name, 'Point', om_0.export()))
|
||||
om_0.update()
|
||||
labelPosition = 'below'
|
||||
}
|
||||
om_0.requiredBy.push(this)
|
||||
}
|
||||
this.om_0 = om_0
|
||||
this.pass = pass
|
||||
if(typeof gain == 'number' || typeof gain == 'string') gain = new MathLib.Expression(gain.toString())
|
||||
this.gain = gain
|
||||
this.labelPosition = labelPosition
|
||||
this.labelX = labelX
|
||||
this.omGraduation = omGraduation
|
||||
}
|
||||
|
||||
getReadableString() {
|
||||
let pass = this.pass == "low" ? qsTr("low-pass") : qsTr("high-pass");
|
||||
return `${this.name}: ${pass}; ${this.om_0.name} = ${this.om_0.x}\n ${' '.repeat(this.name.length)}${this.gain.toString(true)} dB/dec`
|
||||
}
|
||||
|
||||
getLatexString() {
|
||||
let pass = this.pass == "low" ? qsTr("low-pass") : qsTr("high-pass");
|
||||
return `\\mathrm{${Latex.variable(this.name)}:}\\begin{array}{l}
|
||||
\\textsf{${pass}};${Latex.variable(this.om_0.name)} = ${this.om_0.x.latexMarkup} \\\\
|
||||
${this.gain.latexMarkup}\\textsf{ dB/dec}
|
||||
\\end{array}`
|
||||
}
|
||||
|
||||
export() {
|
||||
return [this.name, this.visible, this.color.toString(), this.labelContent,
|
||||
this.om_0.name, this.pass.toString(), this.gain.toEditableString(), this.labelPosition, this.labelX, this.omGraduation]
|
||||
}
|
||||
|
||||
execute(x=1) {
|
||||
if(typeof x == 'string') x = MathLib.executeExpression(x)
|
||||
if((this.pass == 'high' && x < this.om_0.x) || (this.pass == 'low' && x > this.om_0.x)) {
|
||||
var dbfn = new MathLib.Expression(`${this.gain.execute()}*(ln(x)-ln(${this.om_0.x}))/ln(10)+${this.om_0.y}`)
|
||||
return dbfn.execute(x)
|
||||
} else {
|
||||
return this.om_0.y.execute()
|
||||
}
|
||||
}
|
||||
|
||||
simplify(x = 1) {
|
||||
var xval = x
|
||||
if(typeof x == 'string') xval = MathLib.executeExpression(x)
|
||||
if((this.pass == 'high' && xval < this.om_0.x) || (this.pass == 'low' && xval > this.om_0.x)) {
|
||||
var dbfn = new MathLib.Expression(`${this.gain.execute()}*(ln(x)-ln(${this.om_0.x}))/ln(10)+${this.om_0.y}`)
|
||||
return dbfn.simplify(x)
|
||||
} else {
|
||||
return this.om_0.y.toString()
|
||||
}
|
||||
}
|
||||
|
||||
canExecute(x = 1) {
|
||||
return true
|
||||
}
|
||||
|
||||
draw(canvas, ctx) {
|
||||
var base = [canvas.x2px(this.om_0.x), canvas.y2px(this.om_0.y)]
|
||||
var dbfn = new MathLib.Expression(`${this.gain.execute()}*(log10(x)-log10(${this.om_0.x}))+${this.om_0.y}`)
|
||||
var inDrawDom = new MathLib.EmptySet()
|
||||
|
||||
if(this.pass == 'high') {
|
||||
// High pass, linear line from begining, then constant to the end.
|
||||
canvas.drawLine(ctx, base[0], base[1], canvas.canvasSize.width, base[1])
|
||||
inDrawDom = MathLib.parseDomain(`]-inf;${this.om_0.x}[`)
|
||||
} else {
|
||||
// Low pass, constant from the beginning, linear line to the end.
|
||||
canvas.drawLine(ctx, base[0], base[1], 0, base[1])
|
||||
inDrawDom = MathLib.parseDomain(`]${this.om_0.x};+inf[`)
|
||||
}
|
||||
F.Function.drawFunction(canvas, ctx, dbfn, inDrawDom, MathLib.Domain.R)
|
||||
// Dashed line representing break in function
|
||||
var xpos = canvas.x2px(this.om_0.x.execute())
|
||||
var dashPxSize = 10
|
||||
for(var i = 0; i < canvas.canvasSize.height && this.omGraduation; i += dashPxSize*2)
|
||||
canvas.drawLine(ctx, xpos, i, xpos, i+dashPxSize)
|
||||
|
||||
// Label
|
||||
this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))
|
||||
}
|
||||
|
||||
update() {
|
||||
super.update()
|
||||
if(Objects.currentObjects['Somme gains Bode'] != undefined && Objects.currentObjects['Somme gains Bode'].length > 0) {
|
||||
Objects.currentObjects['Somme gains Bode'][0].recalculateCache()
|
||||
} else {
|
||||
Objects.createNewRegisteredObject('Somme gains Bode')
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,128 +0,0 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.pragma library
|
||||
|
||||
.import "common.js" as Common
|
||||
.import "../objects.js" as Objects
|
||||
.import "../mathlib.js" as MathLib
|
||||
.import "../historylib.js" as HistoryLib
|
||||
.import "../parameters.js" as P
|
||||
.import "../math/latex.js" as Latex
|
||||
|
||||
|
||||
class PhaseBode extends Common.ExecutableObject {
|
||||
static type(){return 'Phase Bode'}
|
||||
static displayType(){return qsTr('Bode Phase')}
|
||||
static displayTypeMultiple(){return qsTr('Bode Phases')}
|
||||
static properties() {return {
|
||||
[QT_TRANSLATE_NOOP('prop','om_0')]: new P.ObjectType('Point'),
|
||||
[QT_TRANSLATE_NOOP('prop','phase')]: new P.Expression(),
|
||||
[QT_TRANSLATE_NOOP('prop','unit')]: new P.Enum('°', 'deg', 'rad'),
|
||||
[QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position,
|
||||
[QT_TRANSLATE_NOOP('prop','labelX')]: 'number'
|
||||
}}
|
||||
|
||||
constructor(name = null, visible = true, color = null, labelContent = 'name + value',
|
||||
om_0 = '', phase = 90, unit = '°', labelPosition = 'above', labelX = 1) {
|
||||
if(name == null) name = Common.getNewName('φ')
|
||||
if(name == 'φ') name = 'φ₀' // φ is reserved for sum of BODE phases (Somme phases Bode).
|
||||
super(name, visible, color, labelContent)
|
||||
if(typeof phase == 'number' || typeof phase == 'string') phase = new MathLib.Expression(phase.toString())
|
||||
this.phase = phase
|
||||
if(typeof om_0 == "string") {
|
||||
// Point name or create one
|
||||
om_0 = Objects.currentObjectsByName[om_0]
|
||||
if(om_0 == null) {
|
||||
// Create new point
|
||||
om_0 = Objects.createNewRegisteredObject('Point', [Common.getNewName('ω'), this.color, 'name'])
|
||||
om_0.labelPosition = this.phase.execute() >= 0 ? 'above' : 'below'
|
||||
HistoryLib.history.addToHistory(new HistoryLib.CreateNewObject(om_0.name, 'Point', om_0.export()))
|
||||
labelPosition = 'below'
|
||||
}
|
||||
om_0.requiredBy.push(this)
|
||||
}
|
||||
this.om_0 = om_0
|
||||
this.unit = unit
|
||||
this.labelPosition = labelPosition
|
||||
this.labelX = labelX
|
||||
}
|
||||
|
||||
export() {
|
||||
return [this.name, this.visible, this.color.toString(), this.labelContent,
|
||||
this.om_0.name, this.phase.toEditableString(), this.unit, this.labelPosition, this.labelX]
|
||||
}
|
||||
|
||||
getReadableString() {
|
||||
return `${this.name}: ${this.phase.toString(true)}${this.unit} at ${this.om_0.name} = ${this.om_0.x}`
|
||||
}
|
||||
|
||||
getLatexString() {
|
||||
return `${Latex.variable(this.name)}: ${this.phase.latexMarkup}\\textsf{${this.unit} at }${Latex.variable(this.om_0.name)} = ${this.om_0.x.latexMarkup}`
|
||||
}
|
||||
|
||||
execute(x=1) {
|
||||
if(typeof x == 'string') x = MathLib.executeExpression(x)
|
||||
if(x < this.om_0.x) {
|
||||
return this.om_0.y.execute()
|
||||
} else {
|
||||
return this.om_0.y.execute() + this.phase.execute()
|
||||
}
|
||||
}
|
||||
|
||||
simplify(x = 1) {
|
||||
var xval = x
|
||||
if(typeof x == 'string') xval = MathLib.executeExpression(x)
|
||||
if(xval < this.om_0.x) {
|
||||
return this.om_0.y.toString()
|
||||
} else {
|
||||
var newExp = this.om_0.y.toEditableString() + ' + ' + this.phase.toEditableString()
|
||||
return (new MathLib.Expression(newExp)).toString()
|
||||
}
|
||||
}
|
||||
|
||||
canExecute(x = 1) {
|
||||
return true
|
||||
}
|
||||
|
||||
draw(canvas, ctx) {
|
||||
var baseX = canvas.x2px(this.om_0.x.execute())
|
||||
var omy = this.om_0.y.execute()
|
||||
var augmt = this.phase.execute()
|
||||
var baseY = canvas.y2px(omy)
|
||||
var augmtY = canvas.y2px(omy+augmt)
|
||||
// Before change line.
|
||||
canvas.drawLine(ctx, 0, baseY, Math.min(baseX, canvas.canvasSize.height), baseY)
|
||||
// Transition line.
|
||||
canvas.drawLine(ctx, baseX, baseY, baseX, augmtY)
|
||||
// After change line
|
||||
canvas.drawLine(ctx, Math.max(0, baseX), augmtY, canvas.canvasSize.width, augmtY)
|
||||
|
||||
// Label
|
||||
this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))
|
||||
}
|
||||
|
||||
update() {
|
||||
if(Objects.currentObjects['Somme phases Bode'] != undefined && Objects.currentObjects['Somme phases Bode'].length > 0) {
|
||||
Objects.currentObjects['Somme phases Bode'][0].recalculateCache()
|
||||
} else {
|
||||
Objects.createNewRegisteredObject('Somme phases Bode')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.pragma library
|
||||
|
||||
.import "common.js" as Common
|
||||
.import "../mathlib.js" as MathLib
|
||||
.import "../parameters.js" as P
|
||||
.import "../math/latex.js" as Latex
|
||||
|
||||
|
||||
class Point extends Common.DrawableObject {
|
||||
static type(){return 'Point'}
|
||||
static displayType(){return qsTr('Point')}
|
||||
static displayTypeMultiple(){return qsTr('Points')}
|
||||
|
||||
static properties() {return {
|
||||
[QT_TRANSLATE_NOOP('prop','x')]: new P.Expression(),
|
||||
[QT_TRANSLATE_NOOP('prop','y')]: new P.Expression(),
|
||||
[QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position,
|
||||
[QT_TRANSLATE_NOOP('prop','pointStyle')]: new P.Enum('●', '✕', '+')
|
||||
}}
|
||||
|
||||
constructor(name = null, visible = true, color = null, labelContent = 'name + value',
|
||||
x = 1, y = 0, labelPosition = 'above', pointStyle = '●') {
|
||||
if(name == null) name = Common.getNewName('ABCDEFJKLMNOPQRSTUVW')
|
||||
super(name, visible, color, labelContent)
|
||||
if(typeof x == 'number' || typeof x == 'string') x = new MathLib.Expression(x.toString())
|
||||
this.x = x
|
||||
if(typeof y == 'number' || typeof y == 'string') y = new MathLib.Expression(y.toString())
|
||||
this.y = y
|
||||
this.labelPosition = labelPosition
|
||||
this.pointStyle = pointStyle
|
||||
}
|
||||
|
||||
getReadableString() {
|
||||
return `${this.name} = (${this.x}, ${this.y})`
|
||||
}
|
||||
|
||||
getLatexString() {
|
||||
return `${Latex.variable(this.name)} = \\left(${this.x.latexMarkup}, ${this.y.latexMarkup}\\right)`
|
||||
}
|
||||
|
||||
export() {
|
||||
return [this.name, this.visible, this.color.toString(), this.labelContent, this.x.toEditableString(), this.y.toEditableString(), this.labelPosition, this.pointStyle]
|
||||
}
|
||||
|
||||
draw(canvas, ctx) {
|
||||
var [canvasX, canvasY] = [canvas.x2px(this.x.execute()), canvas.y2px(this.y.execute())]
|
||||
var pointSize = 8+(ctx.lineWidth*2)
|
||||
switch(this.pointStyle) {
|
||||
case '●':
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(canvasX-pointSize/2, canvasY-pointSize/2, pointSize, pointSize)
|
||||
ctx.fill();
|
||||
break;
|
||||
case '✕':
|
||||
canvas.drawLine(ctx, canvasX-pointSize/2, canvasY-pointSize/2, canvasX+pointSize/2, canvasY+pointSize/2)
|
||||
canvas.drawLine(ctx, canvasX-pointSize/2, canvasY+pointSize/2, canvasX+pointSize/2, canvasY-pointSize/2)
|
||||
break;
|
||||
case '+':
|
||||
ctx.fillRect(canvasX-pointSize/2, canvasY-1, pointSize, 2)
|
||||
ctx.fillRect(canvasX-1, canvasY-pointSize/2, 2, pointSize)
|
||||
break;
|
||||
}
|
||||
this.drawLabel(canvas, ctx, this.labelPosition, canvasX, canvasY)
|
||||
}
|
||||
}
|
|
@ -1,153 +0,0 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.pragma library
|
||||
|
||||
.import "common.js" as Common
|
||||
.import "../parameters.js" as P
|
||||
.import "../math/latex.js" as Latex
|
||||
|
||||
|
||||
class RepartitionFunction extends Common.ExecutableObject {
|
||||
static type(){return 'Repartition'}
|
||||
static displayType(){return qsTr('Repartition')}
|
||||
static displayTypeMultiple(){return qsTr('Repartition functions')}
|
||||
static properties() {return {
|
||||
[QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position,
|
||||
[QT_TRANSLATE_NOOP('prop','labelX')]: 'number',
|
||||
'comment1': QT_TRANSLATE_NOOP(
|
||||
'comment',
|
||||
'Note: Specify the probability for each value.'
|
||||
),
|
||||
[QT_TRANSLATE_NOOP('prop','probabilities')]: new P.Dictionary('string', 'float', /^-?[\d.,]+$/, /^-?[\d\.,]+$/, 'P({name_} = ', ') = '),
|
||||
}}
|
||||
|
||||
constructor(name = null, visible = true, color = null, labelContent = 'name + value',
|
||||
beginIncluded = true, drawLineEnds = true, probabilities = {'0': '0'}, labelPosition = 'above', labelX = 1) {
|
||||
if(name == null) name = Common.getNewName('XYZUVW', "F_")
|
||||
super(name, visible, color, labelContent)
|
||||
this.beginIncluded = beginIncluded
|
||||
this.drawLineEnds = drawLineEnds
|
||||
this.probabilities = probabilities
|
||||
this.labelPosition = labelPosition
|
||||
this.labelX = labelX
|
||||
this.update()
|
||||
}
|
||||
|
||||
export() {
|
||||
return [this.name, this.visible, this.color.toString(), this.labelContent,
|
||||
this.beginIncluded, this.drawLineEnds, this.probabilities, this.labelPosition, this.labelX]
|
||||
}
|
||||
|
||||
|
||||
getReadableString() {
|
||||
let keys = Object.keys(this.probabilities).sort((a,b) => a-b);
|
||||
let varname = this.name.substring(this.name.indexOf("_")+1)
|
||||
return `${this.name}(x) = P(${varname} ≤ x)\n` + keys.map(idx => `P(${varname}=${idx})=${this.probabilities[idx]}`).join("; ")
|
||||
}
|
||||
|
||||
getLatexString() {
|
||||
let keys = Object.keys(this.probabilities).sort((a,b) => a-b);
|
||||
let funcName = Latex.variable(this.name)
|
||||
let varName = Latex.variable(this.name.substring(this.name.indexOf("_")+1))
|
||||
return `\\begin{array}{l}{${funcName}}(x) = P(${varName} \\le x)\\\\` + keys.map(idx => `P(${varName}=${idx})=${this.probabilities[idx]}`).join("; ") + '\\end{array}'
|
||||
}
|
||||
|
||||
execute(x = 1) {
|
||||
var ret = 0;
|
||||
Object.keys(this.probabilities).sort((a,b) => a-b).forEach(idx => {
|
||||
if(x >= idx) ret += parseFloat(this.probabilities[idx].replace(/,/g, '.'))
|
||||
})
|
||||
return ret
|
||||
}
|
||||
|
||||
canExecute(x = 1) {return true}
|
||||
// Simplify returns the simplified string of the expression.
|
||||
simplify(x = 1) {
|
||||
return this.execute(x)
|
||||
}
|
||||
|
||||
getLabel() {
|
||||
switch(this.labelContent) {
|
||||
case 'name':
|
||||
return `${this.name}(x)`
|
||||
case 'name + value':
|
||||
return this.getReadableString()
|
||||
case 'null':
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
draw(canvas, ctx) {
|
||||
var currentY = 0;
|
||||
var keys = Object.keys(this.probabilities).map(idx => parseInt(idx)).sort((a,b) => a-b)
|
||||
if(canvas.isVisible(keys[0],this.probabilities[keys[0]].replace(/,/g, '.'))) {
|
||||
canvas.drawLine(ctx,
|
||||
0,
|
||||
canvas.y2px(0),
|
||||
canvas.x2px(keys[0]),
|
||||
canvas.y2px(0)
|
||||
)
|
||||
if(canvas.isVisible(keys[0],0)) {
|
||||
ctx.beginPath();
|
||||
ctx.arc(canvas.x2px(keys[0])+4,canvas.y2px(0), 4, Math.PI / 2, 3 * Math.PI / 2);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
for(var i = 0; i < keys.length-1; i++) {
|
||||
var idx = keys[i];
|
||||
currentY += parseFloat(this.probabilities[idx].replace(/,/g, '.'));
|
||||
if(canvas.isVisible(idx,currentY) || canvas.isVisible(keys[i+1],currentY)) {
|
||||
canvas.drawLine(ctx,
|
||||
Math.max(0,canvas.x2px(idx)),
|
||||
canvas.y2px(currentY),
|
||||
Math.min(canvas.canvasSize.width,canvas.x2px(keys[i+1])),
|
||||
canvas.y2px(currentY)
|
||||
)
|
||||
if(canvas.isVisible(idx,currentY)) {
|
||||
ctx.beginPath();
|
||||
ctx.arc(canvas.x2px(idx),canvas.y2px(currentY), 4, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
}
|
||||
if(canvas.isVisible(keys[i+1],currentY)) {
|
||||
ctx.beginPath();
|
||||
ctx.arc(canvas.x2px(keys[i+1])+4,canvas.y2px(currentY), 4, Math.PI / 2, 3 * Math.PI / 2);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
if(canvas.isVisible(keys[keys.length-1],currentY+parseFloat(this.probabilities[keys[keys.length-1]]))) {
|
||||
canvas.drawLine(ctx,
|
||||
Math.max(0,canvas.x2px(keys[keys.length-1])),
|
||||
canvas.y2px(currentY+parseFloat(this.probabilities[keys[keys.length-1]].replace(/,/g, '.'))),
|
||||
canvas.canvasSize.width,
|
||||
canvas.y2px(currentY+parseFloat(this.probabilities[keys[keys.length-1]].replace(/,/g, '.')))
|
||||
)
|
||||
ctx.beginPath();
|
||||
ctx.arc(
|
||||
canvas.x2px(keys[keys.length-1]),
|
||||
canvas.y2px(currentY+parseFloat(this.probabilities[keys[keys.length-1]].replace(/,/g, '.'))),
|
||||
4, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
// Label
|
||||
this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.pragma library
|
||||
|
||||
.import "common.js" as Common
|
||||
.import "function.js" as F
|
||||
.import "../mathlib.js" as MathLib
|
||||
.import "../parameters.js" as P
|
||||
.import "../math/latex.js" as Latex
|
||||
|
||||
|
||||
class Sequence extends Common.ExecutableObject {
|
||||
static type(){return 'Sequence'}
|
||||
static displayType(){return qsTr('Sequence')}
|
||||
static displayTypeMultiple(){return qsTr('Sequences')}
|
||||
|
||||
static properties() {return {
|
||||
[QT_TRANSLATE_NOOP('prop','drawPoints')]: 'boolean',
|
||||
[QT_TRANSLATE_NOOP('prop','drawDashedLines')]: 'boolean',
|
||||
[QT_TRANSLATE_NOOP('prop','defaultExpression')]: new P.Dictionary('string', 'int', /^.+$/, /^\d+$/, '{name}[n+', '] = ', true),
|
||||
'comment1': QT_TRANSLATE_NOOP(
|
||||
'comment',
|
||||
'Note: Use %1[n] to refer to %1ₙ, %1[n+1] for %1ₙ₊₁...'
|
||||
),
|
||||
[QT_TRANSLATE_NOOP('prop','baseValues')]: new P.Dictionary('string', 'int', /^.+$/, /^\d+$/, '{name}[', '] = '),
|
||||
[QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position,
|
||||
[QT_TRANSLATE_NOOP('prop','labelX')]: 'number',
|
||||
}}
|
||||
|
||||
constructor(name = null, visible = true, color = null, labelContent = 'name + value',
|
||||
drawPoints = true, drawDashedLines = true, defaultExp = {1: "n"},
|
||||
baseValues = {0: 0}, labelPosition = 'above', labelX = 1) {
|
||||
if(name == null) name = Common.getNewName('uvwPSUVWabcde')
|
||||
super(name, visible, color, labelContent)
|
||||
this.drawPoints = drawPoints
|
||||
this.drawDashedLines = drawDashedLines
|
||||
this.defaultExpression = defaultExp
|
||||
this.baseValues = baseValues
|
||||
this.labelPosition = labelPosition
|
||||
this.labelX = labelX
|
||||
this.update()
|
||||
}
|
||||
|
||||
export() {
|
||||
return [this.name, this.visible, this.color.toString(), this.labelContent,
|
||||
this.drawPoints, this.drawDashedLines, this.defaultExpression, this.baseValues, this.labelPosition, this.labelX]
|
||||
}
|
||||
|
||||
update() {
|
||||
super.update()
|
||||
if(
|
||||
this.sequence == null || this.baseValues != this.sequence.baseValues ||
|
||||
this.sequence.name != this.name ||
|
||||
this.sequence.expr != Object.values(this.defaultExpression)[0] ||
|
||||
this.sequence.valuePlus != Object.keys(this.defaultExpression)[0]
|
||||
)
|
||||
this.sequence = new MathLib.Sequence(
|
||||
this.name, this.baseValues,
|
||||
Object.keys(this.defaultExpression)[0],
|
||||
Object.values(this.defaultExpression)[0]
|
||||
)
|
||||
}
|
||||
|
||||
getReadableString() {
|
||||
return this.sequence.toString()
|
||||
}
|
||||
|
||||
getLatexString() {
|
||||
return this.sequence.toLatexString()
|
||||
}
|
||||
|
||||
execute(x = 1) {
|
||||
if(x % 1 == 0)
|
||||
return this.sequence.execute(x)
|
||||
return null
|
||||
}
|
||||
canExecute(x = 1) {return x%1 == 0}
|
||||
|
||||
// Simplify returns the simplified string of the expression.
|
||||
simplify(x = 1) {
|
||||
if(x % 1 == 0)
|
||||
return this.sequence.simplify(x)
|
||||
return null
|
||||
}
|
||||
|
||||
getLabel() {
|
||||
switch(this.labelContent) {
|
||||
case 'name':
|
||||
return `(${this.name}ₙ)`
|
||||
case 'name + value':
|
||||
return this.getReadableString()
|
||||
case 'null':
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
getLatexLabel() {
|
||||
switch(this.labelContent) {
|
||||
case 'name':
|
||||
return `(${Latex.variable(this.name)}_n)`
|
||||
case 'name + value':
|
||||
return this.getLatexString()
|
||||
case 'null':
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
draw(canvas, ctx) {
|
||||
F.Function.drawFunction(canvas, ctx, this.sequence, canvas.logscalex ? MathLib.Domain.NE : MathLib.Domain.N, MathLib.Domain.R, this.drawPoints, this.drawDashedLines)
|
||||
|
||||
// Label
|
||||
this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,145 +0,0 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.pragma library
|
||||
|
||||
.import "common.js" as Common
|
||||
.import "function.js" as F
|
||||
.import "../objects.js" as Objects
|
||||
.import "../mathlib.js" as MathLib
|
||||
.import "../parameters.js" as P
|
||||
.import "../math/latex.js" as Latex
|
||||
|
||||
|
||||
class SommeGainsBode extends Common.ExecutableObject {
|
||||
static type(){return 'Somme gains Bode'}
|
||||
static displayType(){return qsTr('Bode Magnitudes Sum')}
|
||||
static displayTypeMultiple(){return qsTr('Bode Magnitudes Sum')}
|
||||
static createable() {return false}
|
||||
static properties() {return {
|
||||
[QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position,
|
||||
[QT_TRANSLATE_NOOP('prop','labelX')]: 'number',
|
||||
}}
|
||||
|
||||
constructor(name = null, visible = true, color = null, labelContent = 'name + value',
|
||||
labelPosition = 'above', labelX = 1) {
|
||||
if(name == null) name = 'G'
|
||||
super(name, visible, color, labelContent)
|
||||
this.labelPosition = labelPosition
|
||||
this.labelX = labelX
|
||||
this.recalculateCache()
|
||||
}
|
||||
|
||||
export() {
|
||||
return [this.name, this.visible, this.color.toString(), this.labelContent, this.labelPosition, this.labelX]
|
||||
}
|
||||
|
||||
getReadableString() {
|
||||
return `${this.name} = ${Objects.getObjectsName('Gain Bode').join(' + ')}`
|
||||
}
|
||||
|
||||
getLatexString() {
|
||||
return `${Latex.variable(this.name)} = ${Objects.getObjectsName('Gain Bode').map(name => Latex.variable(name)).join(' + ')}`
|
||||
}
|
||||
|
||||
execute(x = 0) {
|
||||
for(var [dbfn, inDrawDom] of this.cachedParts) {
|
||||
if(inDrawDom.includes(x)) {
|
||||
return dbfn.execute(x)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
canExecute(x = 1) {
|
||||
return true // Should always be true
|
||||
}
|
||||
|
||||
simplify(x = 1) {
|
||||
for(var [dbfn, inDrawDom] of this.cachedParts) {
|
||||
if(inDrawDom.includes(x)) {
|
||||
return dbfn.simplify(x)
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
recalculateCache() {
|
||||
this.cachedParts = []
|
||||
// Calculating this is fairly resource expansive so it's cached.
|
||||
if(Objects.currentObjects['Gain Bode'] != undefined) {
|
||||
console.log('Recalculating cache gain')
|
||||
// Minimum to draw (can be expended if needed, just not infinite or it'll cause issues.
|
||||
var drawMin = 0.001
|
||||
|
||||
var baseY = 0
|
||||
var om0xGains = {1000000000: 0} // To draw the last part
|
||||
var om0xPass = {1000000000: 'high'} // To draw the last part
|
||||
Objects.currentObjects['Gain Bode'].forEach(function(gainObj) { // Sorting by their om_0 position.
|
||||
var om0x = gainObj.om_0.x.execute()
|
||||
if(om0xGains[om0x] == undefined) {
|
||||
om0xGains[om0x] = gainObj.gain.execute()
|
||||
om0xPass[om0x] = gainObj.pass == 'high'
|
||||
} else {
|
||||
om0xGains[om0x+0.001] = gainObj.gain.execute()
|
||||
om0xPass[om0x+0.001] = gainObj.pass == 'high'
|
||||
}
|
||||
baseY += gainObj.execute(drawMin)
|
||||
})
|
||||
// Sorting the om_0x
|
||||
var om0xList = Object.keys(om0xGains).map(x => parseFloat(x)) // THEY WERE CONVERTED TO STRINGS...
|
||||
om0xList.sort((a,b) => a - b)
|
||||
// Adding the total gains.
|
||||
var gainsBeforeP = []
|
||||
var gainsAfterP = []
|
||||
var gainTotal = 0
|
||||
for(var om0x of om0xList){
|
||||
if(om0xPass[om0x]) { // High-pass
|
||||
gainsBeforeP.push(om0xGains[om0x])
|
||||
gainsAfterP.push(0)
|
||||
gainTotal += om0xGains[om0x] // Gain at first
|
||||
} else {
|
||||
gainsBeforeP.push(0)
|
||||
gainsAfterP.push(om0xGains[om0x])
|
||||
}
|
||||
}
|
||||
// Calculating parts
|
||||
var previousPallier = drawMin
|
||||
for(var pallier = 0; pallier < om0xList.length; pallier++) {
|
||||
var dbfn = new MathLib.Expression(`${gainTotal}*(ln(x)-ln(${previousPallier}))/ln(10)+${baseY}`)
|
||||
var inDrawDom = MathLib.parseDomain(`]${previousPallier};${om0xList[pallier]}]`)
|
||||
this.cachedParts.push([dbfn, inDrawDom])
|
||||
previousPallier = om0xList[pallier]
|
||||
baseY = dbfn.execute(om0xList[pallier])
|
||||
gainTotal += gainsAfterP[pallier] - gainsBeforeP[pallier]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
draw(canvas, ctx) {
|
||||
if(this.cachedParts.length > 0) {
|
||||
for(var [dbfn, inDrawDom] of this.cachedParts) {
|
||||
F.Function.drawFunction(canvas, ctx, dbfn, inDrawDom, MathLib.Domain.R)
|
||||
if(inDrawDom.includes(this.labelX)) {
|
||||
// Label
|
||||
this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,129 +0,0 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.pragma library
|
||||
|
||||
.import "common.js" as Common
|
||||
.import "../objects.js" as Objects
|
||||
.import "../mathlib.js" as MathLib
|
||||
.import "../parameters.js" as P
|
||||
.import "../math/latex.js" as Latex
|
||||
|
||||
|
||||
class SommePhasesBode extends Common.ExecutableObject {
|
||||
static type(){return 'Somme phases Bode'}
|
||||
static displayType(){return qsTr('Bode Phases Sum')}
|
||||
static displayTypeMultiple(){return qsTr('Bode Phases Sum')}
|
||||
static createable() {return false}
|
||||
static properties() {return {
|
||||
[QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position,
|
||||
[QT_TRANSLATE_NOOP('prop','labelX')]: 'number',
|
||||
}}
|
||||
|
||||
constructor(name = null, visible = true, color = null, labelContent = 'name + value',
|
||||
labelPosition = 'above', labelX = 1) {
|
||||
if(name == null) name = 'φ'
|
||||
super(name, visible, color, labelContent)
|
||||
this.labelPosition = labelPosition
|
||||
this.labelX = labelX
|
||||
this.recalculateCache()
|
||||
}
|
||||
|
||||
export() {
|
||||
return [this.name, this.visible, this.color.toString(), this.labelContent, this.labelPosition, this.labelX]
|
||||
}
|
||||
|
||||
getReadableString() {
|
||||
return `${this.name} = ${Objects.getObjectsName('Phase Bode').join(' + ')}`
|
||||
}
|
||||
|
||||
getLatexString() {
|
||||
return `${Latex.variable(this.name)} = ${Objects.getObjectsName('Phase Bode').map(name => Latex.variable(name)).join(' + ')}`
|
||||
}
|
||||
|
||||
execute(x=1) {
|
||||
if(typeof x == 'string') x = MathLib.executeExpression(x)
|
||||
for(var i = 0; i < this.om0xList.length-1; i++) {
|
||||
if(x >= this.om0xList[i] && x < this.om0xList[i+1]) return this.phasesList[i]
|
||||
}
|
||||
}
|
||||
|
||||
simplify(x = 1) {
|
||||
var xval = x
|
||||
if(typeof x == 'string') xval = MathLib.executeExpression(x)
|
||||
for(var i = 0; i < this.om0xList.length-1; i++) {
|
||||
if(xval >= this.om0xList[i] && xval < this.om0xList[i+1]) {
|
||||
return (new MathLib.Expression(this.phasesExprList[i])).simplify()
|
||||
}
|
||||
}
|
||||
return '0'
|
||||
}
|
||||
|
||||
canExecute(x = 1) {
|
||||
return true
|
||||
}
|
||||
|
||||
recalculateCache() {
|
||||
// Minimum to draw (can be expended if needed, just not infinite or it'll cause issues.
|
||||
var drawMin = 0.001
|
||||
var drawMax = 100000
|
||||
this.om0xList = [drawMin, drawMax]
|
||||
this.phasesList = [0]
|
||||
this.phasesExprList = ['0']
|
||||
var phasesDict = {}
|
||||
var phasesExprDict = {}
|
||||
phasesDict[drawMax] = 0
|
||||
|
||||
if(Objects.currentObjects['Phase Bode'] != undefined) {
|
||||
console.log('Recalculating cache phase')
|
||||
for(var obj of Objects.currentObjects['Phase Bode']) {
|
||||
this.om0xList.push(obj.om_0.x.execute())
|
||||
if(phasesDict[obj.om_0.x.execute()] == undefined) {
|
||||
phasesDict[obj.om_0.x.execute()] = obj.phase.execute()
|
||||
phasesExprDict[obj.om_0.x.execute()] = obj.phase.toEditableString()
|
||||
} else {
|
||||
phasesDict[obj.om_0.x.execute()] += obj.phase.execute()
|
||||
phasesExprDict[obj.om_0.x.execute()] += '+' + obj.phase.toEditableString()
|
||||
}
|
||||
this.phasesList[0] += obj.om_0.y.execute()
|
||||
this.phasesExprList[0] += '+' + obj.om_0.y.toEditableString()
|
||||
}
|
||||
this.om0xList.sort((a,b) => a - b)
|
||||
var totalAdded = this.phasesList[0]
|
||||
for(var i = 1; i < this.om0xList.length; i++) {
|
||||
this.phasesList[i] = this.phasesList[i-1] + phasesDict[this.om0xList[i]]
|
||||
this.phasesExprList[i] = this.phasesExprList[i-1] + '+' + phasesDict[this.om0xList[i]]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
draw(canvas, ctx) {
|
||||
for(var i = 0; i < this.om0xList.length-1; i++) {
|
||||
var om0xBegin = canvas.x2px(this.om0xList[i])
|
||||
var om0xEnd = canvas.x2px(this.om0xList[i+1])
|
||||
var baseY = canvas.y2px(this.phasesList[i])
|
||||
var nextY = canvas.y2px(this.phasesList[i+1])
|
||||
canvas.drawLine(ctx, om0xBegin, baseY, om0xEnd, baseY)
|
||||
canvas.drawLine(ctx, om0xEnd, baseY, om0xEnd, nextY)
|
||||
}
|
||||
|
||||
// Label
|
||||
this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.pragma library
|
||||
|
||||
.import "common.js" as Common
|
||||
.import "../mathlib.js" as MathLib
|
||||
.import "../parameters.js" as P
|
||||
.import "../math/latex.js" as Latex
|
||||
|
||||
|
||||
|
||||
class Text extends Common.DrawableObject {
|
||||
static type(){return 'Text'}
|
||||
static displayType(){return qsTr('Text')}
|
||||
static displayTypeMultiple(){return qsTr('Texts')}
|
||||
static properties() {return {
|
||||
[QT_TRANSLATE_NOOP('prop','x')]: new P.Expression(),
|
||||
[QT_TRANSLATE_NOOP('prop','y')]: new P.Expression(),
|
||||
[QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Positioning,
|
||||
[QT_TRANSLATE_NOOP('prop','text')]: 'string',
|
||||
'comment1': QT_TRANSLATE_NOOP(
|
||||
'comment',
|
||||
'If you have latex enabled, you can use use latex markup in between $$ to create equations.'
|
||||
),
|
||||
[QT_TRANSLATE_NOOP('prop','disableLatex')]: 'boolean'
|
||||
}}
|
||||
|
||||
constructor(name = null, visible = true, color = null, labelContent = 'null',
|
||||
x = 1, y = 0, labelPosition = 'center', text = 'New text', disableLatex = false) {
|
||||
if(name == null) name = Common.getNewName('t')
|
||||
super(name, visible, color, labelContent)
|
||||
if(typeof x == 'number' || typeof x == 'string') x = new MathLib.Expression(x.toString())
|
||||
this.x = x
|
||||
if(typeof y == 'number' || typeof y == 'string') y = new MathLib.Expression(y.toString())
|
||||
this.y = y
|
||||
this.labelPosition = labelPosition
|
||||
this.text = text
|
||||
this.disableLatex = disableLatex
|
||||
}
|
||||
|
||||
getReadableString() {
|
||||
return `${this.name} = "${this.text}"`
|
||||
}
|
||||
|
||||
latexMarkupText() {
|
||||
// Check whether the text contains latex escaped elements.
|
||||
let txt = []
|
||||
this.text.split('$$').forEach(function(t) { txt = txt.concat(Latex.variable(t, true).replace(/\$\$/g, '').split('$')) })
|
||||
let newTxt = txt[0]
|
||||
let i
|
||||
// Split between normal text and latex escaped.
|
||||
for(i = 0; i < txt.length-1; i++)
|
||||
if(i & 0x01) // Every odd number
|
||||
newTxt += '\\textsf{'+Latex.variable(txt[i+1])
|
||||
else
|
||||
newTxt += '}'+txt[i+1]
|
||||
// Finished by a }
|
||||
if(i & 0x01)
|
||||
newTxt += "{"
|
||||
return newTxt
|
||||
}
|
||||
|
||||
getLatexString() {
|
||||
return `${Latex.variable(this.name)} = "\\textsf{${this.latexMarkupText()}}"`
|
||||
}
|
||||
|
||||
export() {
|
||||
return [this.name, this.visible, this.color.toString(), this.labelContent, this.x.toEditableString(), this.y.toEditableString(), this.labelPosition, this.text, this.disableLatex]
|
||||
}
|
||||
|
||||
getLabel() {
|
||||
return this.text
|
||||
}
|
||||
|
||||
getLatexLabel() {
|
||||
return `\\textsf{${this.latexMarkupText()}}`
|
||||
}
|
||||
|
||||
draw(canvas, ctx) {
|
||||
let yOffset = this.disableLatex ? canvas.textsize-4 : 0
|
||||
this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.x.execute()), canvas.y2px(this.y.execute())+yOffset, this.disableLatex)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
class Expression {
|
||||
constructor(...variables) {
|
||||
this.type = 'Expression'
|
||||
this.variables = variables
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.variables.length == 0 ? 'Number' : `Expression(${this.variables.join(', ')})`
|
||||
}
|
||||
}
|
||||
|
||||
class Enum {
|
||||
constructor(...values) {
|
||||
this.type = 'Enum'
|
||||
this.values = values
|
||||
this.translatedValues = values.map(x => qsTr(x))
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.type
|
||||
}
|
||||
}
|
||||
|
||||
class ObjectType {
|
||||
constructor(objType) {
|
||||
this.type = 'ObjectType'
|
||||
this.objType = objType
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.objType
|
||||
}
|
||||
}
|
||||
|
||||
class List {
|
||||
constructor(type, format = /^.+$/, label = '', forbidAdding = false) {
|
||||
// type can be string, int and double.
|
||||
this.type = 'List'
|
||||
this.valueType = type
|
||||
this.format = format
|
||||
this.label = label
|
||||
this.forbidAdding = forbidAdding
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.objType
|
||||
}
|
||||
}
|
||||
|
||||
class Dictionary {
|
||||
constructor(type, keyType = 'string', format = /^.+$/, keyFormat = /^.+$/, preKeyLabel = '', postKeyLabel = ': ', forbidAdding = false) {
|
||||
// type & keyType can be string, int and double.
|
||||
this.type = 'Dict'
|
||||
this.valueType = type
|
||||
this.keyType = keyType
|
||||
this.format = format
|
||||
this.keyFormat = keyFormat
|
||||
this.preKeyLabel = preKeyLabel
|
||||
this.postKeyLabel = postKeyLabel
|
||||
this.forbidAdding = forbidAdding
|
||||
}
|
||||
|
||||
toString() {
|
||||
return 'Dictionary'
|
||||
}
|
||||
}
|
||||
|
||||
// Common parameters for Enums
|
||||
|
||||
Enum.Position = new Enum(
|
||||
QT_TR_NOOP('above'),
|
||||
QT_TR_NOOP('below'),
|
||||
QT_TR_NOOP('left'),
|
||||
QT_TR_NOOP('right'),
|
||||
QT_TR_NOOP('above-left'),
|
||||
QT_TR_NOOP('above-right'),
|
||||
QT_TR_NOOP('below-left'),
|
||||
QT_TR_NOOP('below-right')
|
||||
)
|
||||
|
||||
Enum.Positioning = new Enum(
|
||||
QT_TR_NOOP('center'),
|
||||
QT_TR_NOOP('top'),
|
||||
QT_TR_NOOP('bottom'),
|
||||
QT_TR_NOOP('left'),
|
||||
QT_TR_NOOP('right'),
|
||||
QT_TR_NOOP('top-left'),
|
||||
QT_TR_NOOP('top-right'),
|
||||
QT_TR_NOOP('bottom-left'),
|
||||
QT_TR_NOOP('bottom-right')
|
||||
)
|
||||
|
||||
Enum.FunctionDisplayType = new Enum(
|
||||
QT_TR_NOOP('application'),
|
||||
QT_TR_NOOP('function')
|
||||
)
|
||||
|
||||
Enum.BodePass = new Enum(
|
||||
QT_TR_NOOP('high'),
|
||||
QT_TR_NOOP('low')
|
||||
)
|
||||
|
||||
|
||||
Enum.XCursorValuePosition = new Enum(
|
||||
QT_TR_NOOP('Next to target'),
|
||||
QT_TR_NOOP('With label'),
|
||||
QT_TR_NOOP('Hidden')
|
||||
)
|
||||
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// Contains polyfill math functions used for reference.
|
||||
|
||||
.pragma library
|
||||
|
||||
function factorial(x) {
|
||||
if (x < 0) // Integrating by less than 0
|
||||
if(isFinite(n))
|
||||
return Infinity
|
||||
else
|
||||
throw new EvalError("Cannot calculate the factorial of -∞.")
|
||||
|
||||
return gamma(x+1)
|
||||
}
|
||||
|
||||
let GAMMA_G = 4.7421875
|
||||
let GAMMA_P = [
|
||||
0.99999999999999709182,
|
||||
57.156235665862923517, -59.597960355475491248,
|
||||
14.136097974741747174, -0.49191381609762019978,
|
||||
0.33994649984811888699e-4,
|
||||
0.46523628927048575665e-4, -0.98374475304879564677e-4,
|
||||
0.15808870322491248884e-3, -0.21026444172410488319e-3,
|
||||
0.21743961811521264320e-3, -0.16431810653676389022e-3,
|
||||
0.84418223983852743293e-4, -0.26190838401581408670e-4,
|
||||
0.36899182659531622704e-5
|
||||
]
|
||||
|
||||
function gamma(n) {
|
||||
if(n <= 0) // Integrating by less than 0
|
||||
if(isFinite(n))
|
||||
return Infinity
|
||||
else
|
||||
throw new EvalError("Cannot calculate Γ(-∞).")
|
||||
|
||||
if(n >= 171.35)
|
||||
return Infinity // Would return more than 2^1024 - 1 (aka Number.INT_MAX)
|
||||
|
||||
if(n === Math.round(n) && isFinite(n)) {
|
||||
// Calculating (n-1)!
|
||||
let res = n - 1
|
||||
|
||||
for(let i = n - 2; i > 1; i++)
|
||||
res *= i
|
||||
|
||||
if(res === 0)
|
||||
res = 1 // 0! is per definition 1
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// Section below adapted function adapted from math.js
|
||||
if(n < 0.5)
|
||||
return Math.PI / (Math.sin(Math.PI * n) * gamma(1 - n))
|
||||
|
||||
if(n > 85.0) { // Extended Stirling Approx
|
||||
let twoN = n * n
|
||||
let threeN = twoN * n
|
||||
let fourN = threeN * n
|
||||
let fiveN = fourN * n
|
||||
return Math.sqrt(2 * Math.PI / n) * Math.pow((n / Math.E), n) *
|
||||
(1 + (1 / (12 * n)) + (1 / (288 * twoN)) - (139 / (51840 * threeN)) -
|
||||
(571 / (2488320 * fourN)) + (163879 / (209018880 * fiveN)) +
|
||||
(5246819 / (75246796800 * fiveN * n)))
|
||||
}
|
||||
|
||||
--n
|
||||
let x = GAMMA_P[0]
|
||||
for (let i = 1; i < GAMMA_P.length; ++i) {
|
||||
x += GAMMA_P[i] / (n + i)
|
||||
}
|
||||
|
||||
let t = n + GAMMA_G + 0.5
|
||||
return Math.sqrt(2 * Math.PI) * Math.pow(t, n + 0.5) * Math.exp(-t) * x
|
||||
}
|
||||
|
||||
function arrayMap(f, arr) {
|
||||
if (typeof f != 'function')
|
||||
throw new EvalError(qsTranslate('error', 'First argument to map is not a function.'))
|
||||
if (!Array.isArray(arr))
|
||||
throw new EvalError(qsTranslate('error', 'Second argument to map is not an array.'))
|
||||
return arr.map(f)
|
||||
}
|
||||
|
||||
function arrayFold(f, init, arr) {
|
||||
if (typeof f != 'function')
|
||||
throw new EvalError(qsTranslate('error', 'First argument to fold is not a function.'))
|
||||
if (!Array.isArray(arr))
|
||||
throw new EvalError(qsTranslate('error', 'Second argument to fold is not an array.'))
|
||||
return arr.reduce(f, init)
|
||||
}
|
||||
|
||||
function arrayFilter(f, arr) {
|
||||
if (typeof f != 'function')
|
||||
throw new EvalError(qsTranslate('error', 'First argument to filter is not a function.'))
|
||||
if (!Array.isArray(arr))
|
||||
throw new EvalError(qsTranslate('error', 'Second argument to filter is not an array.'))
|
||||
return arr.filter(f)
|
||||
}
|
||||
|
||||
function arrayFilter(f, arr) {
|
||||
if (typeof f != 'function')
|
||||
throw new EvalError(qsTranslate('error', 'First argument to filter is not a function.'))
|
||||
if (!Array.isArray(arr))
|
||||
throw new EvalError(qsTranslate('error', 'Second argument to filter is not an array.'))
|
||||
return arr.filter(f)
|
||||
}
|
||||
|
||||
function arrayJoin(sep, arr) {
|
||||
if (!Array.isArray(arr))
|
||||
throw new Error(qsTranslate('error', 'Second argument to join is not an array.'))
|
||||
return arr.join(sep)
|
||||
}
|
||||
|
||||
function indexOf(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)
|
||||
}
|
|
@ -1,177 +0,0 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.pragma library
|
||||
|
||||
.import "polyfill.js" as Polyfill
|
||||
|
||||
|
||||
const CONSTANTS = {
|
||||
"π": Math.PI,
|
||||
"pi": Math.PI,
|
||||
"inf": Infinity,
|
||||
"infinity": Infinity,
|
||||
"∞": Infinity,
|
||||
"e": Math.E
|
||||
};
|
||||
const CONSTANTS_LIST = Object.keys(CONSTANTS);
|
||||
|
||||
const FUNCTIONS = {
|
||||
// The functions commented are the one either not implemented
|
||||
// in the parser, or not to be used for autocompletion.
|
||||
// Unary operators
|
||||
//'+': Number,
|
||||
//'-': (x) => -x,
|
||||
//'!'
|
||||
// Other operations
|
||||
'length': (s) => Array.isArray(s) ? s.length : String(s).length,
|
||||
// Boolean functions
|
||||
'not': (x) => !x,
|
||||
// Math functions
|
||||
'abs': Math.abs,
|
||||
'acos': Math.acos,
|
||||
'acosh': Math.acosh,
|
||||
'asin': Math.asin,
|
||||
'asinh': Math.asinh,
|
||||
'atan': Math.atan,
|
||||
'atan2': Math.atan2,
|
||||
'atanh': Math.atanh,
|
||||
'cbrt': Math.cbrt,
|
||||
'ceil': Math.ceil,
|
||||
//'clz32': Math.clz32,
|
||||
'cos': Math.cos,
|
||||
'cosh': Math.cosh,
|
||||
'exp': Math.exp,
|
||||
'expm1': Math.expm1,
|
||||
'floor': Math.floor,
|
||||
//'fround': Math.fround,
|
||||
'hypot': Math.hypot,
|
||||
//'imul': Math.imul,
|
||||
'lg': Math.log10,
|
||||
'ln': Math.log,
|
||||
'log': Math.log,
|
||||
'log10': Math.log10,
|
||||
'log1p': Math.log1p,
|
||||
'log2': Math.log2,
|
||||
'max': Math.max,
|
||||
'min': Math.min,
|
||||
'pow': Math.log2,
|
||||
'random': Math.random,
|
||||
'round': Math.round,
|
||||
'sign': Math.sign,
|
||||
'sin': Math.sin,
|
||||
'sinh': Math.sinh,
|
||||
'sqrt': Math.sqrt,
|
||||
'tan': Math.tan,
|
||||
'tanh': Math.tanh,
|
||||
'trunc': Math.trunc,
|
||||
// Functions in expr-eval, ported here.
|
||||
'fac': Polyfill.factorial,
|
||||
'gamma': Polyfill.gamma,
|
||||
'Γ': Polyfill.gamma,
|
||||
'roundTo': (x, exp) => Number(x).toFixed(exp),
|
||||
// 'map': Polyfill.arrayMap,
|
||||
// 'fold': Polyfill.arrayFold,
|
||||
// 'filter': Polyfill.arrayFilter,
|
||||
// 'indexOf': Polyfill.indexOf,
|
||||
// 'join': Polyfill.arrayJoin,
|
||||
// Integral & derivative (only here for autocomplete).
|
||||
'integral': () => 0, // TODO: Implement
|
||||
'derivative': () => 0,
|
||||
}
|
||||
const FUNCTIONS_LIST = Object.keys(FUNCTIONS);
|
||||
|
||||
class P {
|
||||
// Parameter class.
|
||||
constructor(type, name = '', optional = false, multipleAllowed = false) {
|
||||
this.name = name
|
||||
this.type = type
|
||||
this.optional = optional
|
||||
this.multipleAllowed = multipleAllowed
|
||||
}
|
||||
|
||||
toString() {
|
||||
let base_string = this.type
|
||||
if(this.name != '')
|
||||
base_string = `${this.name}: ${base_string}`
|
||||
if(this.multipleAllowed)
|
||||
base_string += '...'
|
||||
if(!this.optional)
|
||||
base_string = `<${base_string}>`
|
||||
else
|
||||
base_string = `[${base_string}]`
|
||||
return base_string
|
||||
}
|
||||
}
|
||||
|
||||
let string = new P('string')
|
||||
let bool = new P('bool')
|
||||
let number = new P('number')
|
||||
let array = new P('array')
|
||||
|
||||
const FUNCTIONS_USAGE = {
|
||||
'length': [string],
|
||||
'not': [bool],
|
||||
// Math functions
|
||||
'abs': [number],
|
||||
'acos': [number],
|
||||
'acosh': [number],
|
||||
'asin': [number],
|
||||
'asinh': [number],
|
||||
'atan': [number],
|
||||
'atan2': [number],
|
||||
'atanh': [number],
|
||||
'cbrt': [number],
|
||||
'ceil': [number],
|
||||
//'clz32': [number],
|
||||
'cos': [number],
|
||||
'cosh': [number],
|
||||
'exp': [number],
|
||||
'expm1': [number],
|
||||
'floor': [number],
|
||||
//'fround': [number],
|
||||
'hypot': [number],
|
||||
//'imul': [number],
|
||||
'lg': [number],
|
||||
'ln': [number],
|
||||
'log': [number],
|
||||
'log10': [number],
|
||||
'log1p': [number],
|
||||
'log2': [number],
|
||||
'max': [number, number, new P('numbers', '', true, true)],
|
||||
'min': [number, number, new P('numbers', '', true, true)],
|
||||
'pow': [number, new P('number', 'exp')],
|
||||
'random': [number, number],
|
||||
'round': [number],
|
||||
'sign': [number],
|
||||
'sin': [number],
|
||||
'sinh': [number],
|
||||
'sqrt': [number],
|
||||
'tan': [number],
|
||||
'tanh': [number],
|
||||
'trunc': [number],
|
||||
// Functions in expr-eval, ported here.
|
||||
'fac': [number],
|
||||
'gamma': [number],
|
||||
'Γ': [number],
|
||||
'roundTo': [number, new P('number')],
|
||||
// Function manipulation
|
||||
'derivative': [new P('f'), new P('string', 'var', true), number],
|
||||
'integral': [new P('from'), new P('to'), new P('f'), new P('string', 'var', true)],
|
||||
}
|
||||
|
|
@ -1,369 +0,0 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.pragma library
|
||||
|
||||
var powerpos = {
|
||||
"-": "⁻",
|
||||
"+": "⁺",
|
||||
"=": "⁼",
|
||||
" ": " ",
|
||||
"(": "⁽",
|
||||
")": "⁾",
|
||||
"0": "⁰",
|
||||
"1": "¹",
|
||||
"2": "²",
|
||||
"3": "³",
|
||||
"4": "⁴",
|
||||
"5": "⁵",
|
||||
"6": "⁶",
|
||||
"7": "⁷",
|
||||
"8": "⁸",
|
||||
"9": "⁹",
|
||||
"a": "ᵃ",
|
||||
"b": "ᵇ",
|
||||
"c": "ᶜ",
|
||||
"d": "ᵈ",
|
||||
"e": "ᵉ",
|
||||
"f": "ᶠ",
|
||||
"g": "ᵍ",
|
||||
"h": "ʰ",
|
||||
"i": "ⁱ",
|
||||
"j": "ʲ",
|
||||
"k": "ᵏ",
|
||||
"l": "ˡ",
|
||||
"m": "ᵐ",
|
||||
"n": "ⁿ",
|
||||
"o": "ᵒ",
|
||||
"p": "ᵖ",
|
||||
"r": "ʳ",
|
||||
"s": "ˢ",
|
||||
"t": "ᵗ",
|
||||
"u": "ᵘ",
|
||||
"v": "ᵛ",
|
||||
"w": "ʷ",
|
||||
"x": "ˣ",
|
||||
"y": "ʸ",
|
||||
"z": "ᶻ"
|
||||
}
|
||||
|
||||
var exponents = [
|
||||
"⁰","¹","²","³","⁴","⁵","⁶","⁷","⁸","⁹"
|
||||
]
|
||||
var exponentReg = new RegExp('(['+exponents.join('')+']+)', 'g')
|
||||
|
||||
var indicepos = {
|
||||
"-": "₋",
|
||||
"+": "₊",
|
||||
"=": "₌",
|
||||
"(": "₍",
|
||||
")": "₎",
|
||||
" ": " ",
|
||||
"0": "₀",
|
||||
"1": "₁",
|
||||
"2": "₂",
|
||||
"3": "₃",
|
||||
"4": "₄",
|
||||
"5": "₅",
|
||||
"6": "₆",
|
||||
"7": "₇",
|
||||
"8": "₈",
|
||||
"9": "₉",
|
||||
"a": "ₐ",
|
||||
"e": "ₑ",
|
||||
"h": "ₕ",
|
||||
"i": "ᵢ",
|
||||
"j": "ⱼ",
|
||||
"k": "ₖ",
|
||||
"l": "ₗ",
|
||||
"m": "ₘ",
|
||||
"n": "ₙ",
|
||||
"o": "ₒ",
|
||||
"p": "ₚ",
|
||||
"r": "ᵣ",
|
||||
"s": "ₛ",
|
||||
"t": "ₜ",
|
||||
"u": "ᵤ",
|
||||
"v": "ᵥ",
|
||||
"x": "ₓ",
|
||||
}
|
||||
// Put a text in sup position
|
||||
function textsup(text) {
|
||||
var ret = ""
|
||||
text = text.toString()
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
if(Object.keys(powerpos).indexOf(text[i]) >= 0) {
|
||||
ret += powerpos[text[i]]
|
||||
} else {
|
||||
ret += text[i]
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Put a text in sub position
|
||||
function textsub(text) {
|
||||
var ret = ""
|
||||
text = text.toString()
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
if(Object.keys(indicepos).indexOf(text[i]) >= 0) {
|
||||
ret += indicepos[text[i]]
|
||||
} else {
|
||||
ret += text[i]
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
function simplifyExpression(str) {
|
||||
var replacements = [
|
||||
// Operations not done by parser.
|
||||
// [// Decomposition way 2
|
||||
// /(^|[+-] |\()([-.\d\w]+) ([*/]) \((([-.\d\w] [*/] )?[-\d\w.]+) ([+\-]) (([-.\d\w] [*/] )?[\d\w.+]+)\)($| [+-]|\))/g,
|
||||
// "$1$2 $3 $4 $6 $2 $3 $7$9"
|
||||
// ],
|
||||
// [ // Decomposition way 2
|
||||
// /(^|[+-] |\()\((([-.\d\w] [*/] )?[-\d\w.]+) ([+\-]) (([-.\d\w] [*/] )?[\d\w.+]+)\) ([*/]) ([-.\d\w]+)($| [+-]|\))/g,
|
||||
// "$1$2 $7 $8 $4 $5 $7 $8$9"
|
||||
// ],
|
||||
[ // Factorisation of π elements.
|
||||
/(([-\d\w.]+ [*/] )*)(pi|π)(( [/*] [-\d\w.]+)*) ([+-]) (([-\d\w.]+ [*/] )*)(pi|π)(( [/*] [-\d\w.]+)*)?/g,
|
||||
function(match, m1, n1, pi1, m2, ope2, n2, opeM, m3, n3, pi2, m4, ope4, n4) {
|
||||
// g1, g2, g3 , g4, g5, g6, g7, g8, g9, g10, g11,g12 , g13
|
||||
// We don't care about mx & pix, ope2 & ope4 are either / or * for n2 & n4.
|
||||
// n1 & n3 are multiplied, opeM is the main operation (- or +).
|
||||
// Putting all n in form of number
|
||||
//n2 = n2 == undefined ? 1 : parseFloat(n)
|
||||
n1 = m1 == undefined ? 1 : eval(m1 + '1')
|
||||
n2 = m2 == undefined ? 1 : eval('1' + m2)
|
||||
n3 = m3 == undefined ? 1 : eval(m3 + '1')
|
||||
n4 = m4 == undefined ? 1 : eval('1' + m4)
|
||||
//var [n1, n2, n3, n4] = [n1, n2, n3, n4].map(n => n == undefined ? 1 : parseFloat(n))
|
||||
// Falling back to * in case it does not exist (the corresponding n would be 1)
|
||||
var [ope2, ope4] = [ope2, ope4].map(ope => ope == '/' ? '/' : '*')
|
||||
var coeff1 = n1*n2
|
||||
var coeff2 = n3*n4
|
||||
var coefficient = coeff1+coeff2-(opeM == '-' ? 2*coeff2 : 0)
|
||||
|
||||
return `${coefficient} * π`
|
||||
}
|
||||
],
|
||||
[ // Removing parenthesis when content is only added from both sides.
|
||||
/(^|[+-] |\()\(([^)(]+)\)($| [+-]|\))/g,
|
||||
function(match, b4, middle, after) {return `${b4}${middle}${after}`}
|
||||
],
|
||||
[ // Removing parenthesis when content is only multiplied.
|
||||
/(^|[*\/] |\()\(([^)(+-]+)\)($| [*\/+-]|\))/g,
|
||||
function(match, b4, middle, after) {return `${b4}${middle}${after}`}
|
||||
],
|
||||
[ // Removing parenthesis when content is only multiplied.
|
||||
/(^|[*\/-+] |\()\(([^)(+-]+)\)($| [*\/]|\))/g,
|
||||
function(match, b4, middle, after) {return `${b4}${middle}${after}`}
|
||||
],
|
||||
[// Simplification additions/substractions.
|
||||
/(^|[^*\/] |\()([-.\d]+) (\+|\-) (\([^)(]+\)|[^)(]+) (\+|\-) ([-.\d]+)($| [^*\/]|\))/g,
|
||||
function(match, b4, n1, op1, middle, op2, n2, after) {
|
||||
var total
|
||||
if(op2 == '+') {
|
||||
total = parseFloat(n1) + parseFloat(n2)
|
||||
} else {
|
||||
total = parseFloat(n1) - parseFloat(n2)
|
||||
}
|
||||
return `${b4}${total} ${op1} ${middle}${after}`
|
||||
}
|
||||
],
|
||||
[// Simplification multiplications/divisions.
|
||||
/([-.\d]+) (\*|\/) (\([^)(]+\)|[^)(+-]+) (\*|\/) ([-.\d]+)/g,
|
||||
function(match, n1, op1, middle, op2, n2) {
|
||||
if(parseInt(n1) == n1 && parseInt(n2) == n2 && op2 == '/' &&
|
||||
(parseInt(n1) / parseInt(n2)) % 1 != 0) {
|
||||
// Non int result for int division.
|
||||
return `(${n1} / ${n2}) ${op1} ${middle}`
|
||||
} else {
|
||||
if(op2 == '*') {
|
||||
return `${parseFloat(n1) * parseFloat(n2)} ${op1} ${middle}`
|
||||
} else {
|
||||
return `${parseFloat(n1) / parseFloat(n2)} ${op1} ${middle}`
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
[// Starting & ending parenthesis if not needed.
|
||||
/^\s*\((.*)\)\s*$/g,
|
||||
function(match, middle) {
|
||||
var str = middle
|
||||
// Replace all groups
|
||||
while(/\([^)(]+\)/g.test(str))
|
||||
str = str.replace(/\([^)(]+\)/g, '')
|
||||
// There shouldn't be any more parenthesis
|
||||
// If there is, that means the 2 parenthesis are needed.
|
||||
if(!str.includes(')') && !str.includes('(')) {
|
||||
return middle
|
||||
} else {
|
||||
return `(${middle})`
|
||||
}
|
||||
|
||||
}
|
||||
],
|
||||
// Simple simplifications
|
||||
// [/(\s|^|\()0(\.0+)? \* (\([^)(]+\))/g, '$10'],
|
||||
// [/(\s|^|\()0(\.0+)? \* ([^)(+-]+)/g, '$10'],
|
||||
// [/(\([^)(]\)) \* 0(\.0+)?(\s|$|\))/g, '0$3'],
|
||||
// [/([^)(+-]) \* 0(\.0+)?(\s|$|\))/g, '0$3'],
|
||||
// [/(\s|^|\()1(\.0+)? (\*|\/) /g, '$1'],
|
||||
// [/(\s|^|\()0(\.0+)? (\+|\-) /g, '$1'],
|
||||
// [/ (\*|\/) 1(\.0+)?(\s|$|\))/g, '$3'],
|
||||
// [/ (\+|\-) 0(\.0+)?(\s|$|\))/g, '$3'],
|
||||
// [/(^| |\() /g, '$1'],
|
||||
// [/ ($|\))/g, '$1'],
|
||||
]
|
||||
|
||||
// Replacements
|
||||
var found
|
||||
do {
|
||||
found = false
|
||||
for(var replacement of replacements)
|
||||
while(replacement[0].test(str)) {
|
||||
found = true
|
||||
str = str.replace(replacement[0], replacement[1])
|
||||
}
|
||||
} while(found)
|
||||
return str
|
||||
}
|
||||
|
||||
|
||||
function makeExpressionReadable(str) {
|
||||
var replacements = [
|
||||
// variables
|
||||
[/pi/g, 'π'],
|
||||
[/Infinity/g, '∞'],
|
||||
[/inf/g, '∞'],
|
||||
// Other
|
||||
[/ \* /g, '×'],
|
||||
[/ \^ /g, '^'],
|
||||
[/\^\(([\d\w+-]+)\)/g, function(match, p1) { return textsup(p1) }],
|
||||
[/\^([\d\w+-]+)/g, function(match, p1) { return textsup(p1) }],
|
||||
[/_\(([\d\w+-]+)\)/g, function(match, p1) { return textsub(p1) }],
|
||||
[/_([\d\w+-]+)/g, function(match, p1) { return textsub(p1) }],
|
||||
[/\[([^\[\]]+)\]/g, function(match, p1) { return textsub(p1) }],
|
||||
[/(\d|\))×/g, '$1'],
|
||||
//[/×(\d|\()/g, '$1'],
|
||||
[/([^a-z])\(([^)(+.\/-]+)\)/g, "$1×$2"],
|
||||
[/integral\((.+),\s?(.+),\s?("|')(.+)("|'),\s?("|')(.+)("|')\)/g, function(match, a, b, p1, body, p2, p3, by, p4) {
|
||||
if(a.length < b.length) {
|
||||
return `∫${textsub(a)}${textsup(b)} ${body} d${by}`
|
||||
} else {
|
||||
return `∫${textsup(b)}${textsub(a)} ${body} d${by}`
|
||||
}
|
||||
}],
|
||||
[/derivative\(?("|')(.+)("|'), ?("|')(.+)("|'), ?(.+)\)?/g, function(match, p1, body, p2, p3, by, p4, x) {
|
||||
return `d(${body.replace(new RegExp(by, 'g'), 'x')})/dx`
|
||||
}]
|
||||
]
|
||||
|
||||
// str = simplifyExpression(str)
|
||||
// Replacements
|
||||
for(var replacement of replacements)
|
||||
while(replacement[0].test(str))
|
||||
str = str.replace(replacement[0], replacement[1])
|
||||
return str
|
||||
}
|
||||
|
||||
function parseName(str, removeUnallowed = true) {
|
||||
var replacements = [
|
||||
// Greek letters
|
||||
[/([^a-z]|^)al(pha)?([^a-z]|$)/g, '$1α$3'],
|
||||
[/([^a-z]|^)be(ta)?([^a-z]|$)/g, '$1β$3'],
|
||||
[/([^a-z]|^)ga(mma)?([^a-z]|$)/g, '$1γ$3'],
|
||||
[/([^a-z]|^)de(lta)?([^a-z]|$)/g, '$1δ$3'],
|
||||
[/([^a-z]|^)ep(silon)?([^a-z]|$)/g, '$1ε$3'],
|
||||
[/([^a-z]|^)ze(ta)?([^a-z]|$)/g, '$1ζ$3'],
|
||||
[/([^a-z]|^)et(a)?([^a-z]|$)/g, '$1η$3'],
|
||||
[/([^a-z]|^)th(eta)?([^a-z]|$)/g, '$1θ$3'],
|
||||
[/([^a-z]|^)io(ta)?([^a-z]|$)/g, '$1ι$3'],
|
||||
[/([^a-z]|^)ka(ppa)([^a-z]|$)?/g, '$1κ$3'],
|
||||
[/([^a-z]|^)la(mbda)?([^a-z]|$)/g, '$1λ$3'],
|
||||
[/([^a-z]|^)mu([^a-z]|$)/g, '$1μ$2'],
|
||||
[/([^a-z]|^)nu([^a-z]|$)/g, '$1ν$2'],
|
||||
[/([^a-z]|^)xi([^a-z]|$)/g, '$1ξ$2'],
|
||||
[/([^a-z]|^)rh(o)?([^a-z]|$)/g, '$1ρ$3'],
|
||||
[/([^a-z]|^)si(gma)?([^a-z]|$)/g, '$1σ$3'],
|
||||
[/([^a-z]|^)ta(u)?([^a-z]|$)/g, '$1τ$3'],
|
||||
[/([^a-z]|^)up(silon)?([^a-z]|$)/g, '$1υ$3'],
|
||||
[/([^a-z]|^)ph(i)?([^a-z]|$)/g, '$1φ$3'],
|
||||
[/([^a-z]|^)ch(i)?([^a-z]|$)/g, '$1χ$3'],
|
||||
[/([^a-z]|^)ps(i)?([^a-z]|$)/g, '$1ψ$3'],
|
||||
[/([^a-z]|^)om(ega)?([^a-z]|$)/g, '$1ω$3'],
|
||||
// Capital greek letters
|
||||
[/([^a-z]|^)gga(mma)?([^a-z]|$)/g, '$1Γ$3'],
|
||||
[/([^a-z]|^)gde(lta)?([^a-z]|$)/g, '$1Δ$3'],
|
||||
[/([^a-z]|^)gth(eta)?([^a-z]|$)/g, '$1Θ$3'],
|
||||
[/([^a-z]|^)gla(mbda)?([^a-z]|$)/g, '$1Λ$3'],
|
||||
[/([^a-z]|^)gxi([^a-z]|$)/g, '$1Ξ$2'],
|
||||
[/([^a-z]|^)gpi([^a-z]|$)/g, '$1Π$2'],
|
||||
[/([^a-z]|^)gsi(gma)([^a-z]|$)?/g, '$1Σ$3'],
|
||||
[/([^a-z]|^)gph(i)?([^a-z]|$)/g, '$1Φ$3'],
|
||||
[/([^a-z]|^)gps(i)?([^a-z]|$)/g, '$1Ψ$3'],
|
||||
[/([^a-z]|^)gom(ega)?([^a-z]|$)/g, '$1Ω$3'],
|
||||
// Underscores
|
||||
// [/_\(([^_]+)\)/g, function(match, p1) { return textsub(p1) }],
|
||||
// [/_([^" ]+)/g, function(match, p1) { return textsub(p1) }],
|
||||
// Array elements
|
||||
[/\[([^\]\[]+)\]/g, function(match, p1) { return textsub(p1) }],
|
||||
// Removing
|
||||
[/[xπℝℕ\\∪∩\]\[ ()^/÷*×+=\d-]/g , ''],
|
||||
]
|
||||
if(!removeUnallowed) replacements.pop()
|
||||
// Replacements
|
||||
for(var replacement of replacements)
|
||||
str = str.replace(replacement[0], replacement[1])
|
||||
return str
|
||||
}
|
||||
|
||||
String.prototype.toLatinUppercase = function() {
|
||||
return this.replace(/[a-z]/g, function(match){return match.toUpperCase()})
|
||||
}
|
||||
|
||||
function camelCase2readable(label) {
|
||||
var parsed = parseName(label, false)
|
||||
return parsed.charAt(0).toLatinUppercase() + parsed.slice(1).replace(/([A-Z])/g," $1")
|
||||
}
|
||||
|
||||
function getRandomColor() {
|
||||
var clrs = '0123456789ABCDEF';
|
||||
var color = '#';
|
||||
for(var i = 0; i < 6; i++) {
|
||||
color += clrs[Math.floor(Math.random() * (16-5*(i%2==0)))];
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
function escapeHTML(str) {
|
||||
return str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>') ;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Parses exponents and replaces them with expression values
|
||||
* @param {string} expression - The expression to replace in.
|
||||
* @return {string} The parsed expression
|
||||
*/
|
||||
function exponentsToExpression(expression) {
|
||||
return expression.replace(exponentReg, (m, exp) => '^' + exp.split('').map((x) => exponents.indexOf(x)).join(''))
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
module eu.ad5001.LogarithmPlotter
|
||||
|
||||
Settings 1.0 Settings.qml
|
||||
Alert 1.0 Alert.qml
|
|
@ -1,170 +0,0 @@
|
|||
"""
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from PySide6.QtWidgets import QMessageBox, QApplication
|
||||
from PySide6.QtCore import QRunnable, QThreadPool, QThread, QObject, Signal, Slot, QCoreApplication
|
||||
from PySide6.QtQml import QQmlApplicationEngine
|
||||
from PySide6.QtGui import QImage
|
||||
from PySide6 import __version__ as PySide6_version
|
||||
|
||||
from os import chdir, path
|
||||
from json import loads
|
||||
from sys import version as sys_version
|
||||
from urllib.request import urlopen
|
||||
from urllib.error import HTTPError, URLError
|
||||
|
||||
from LogarithmPlotter import __VERSION__
|
||||
from LogarithmPlotter.util import config
|
||||
|
||||
class ChangelogFetcher(QRunnable):
|
||||
def __init__(self, helper):
|
||||
QRunnable.__init__(self)
|
||||
self.helper = helper
|
||||
|
||||
def run(self):
|
||||
msg_text = "Unknown changelog error."
|
||||
try:
|
||||
# Fetching version
|
||||
r = urlopen("https://api.ad5001.eu/changelog/logarithmplotter/?version=" + __VERSION__)
|
||||
lines = r.readlines()
|
||||
r.close()
|
||||
msg_text = "".join(map(lambda x: x.decode('utf-8'), lines)).strip()
|
||||
except HTTPError as e:
|
||||
msg_text = QCoreApplication.translate("changelog","Could not fetch changelog: Server error {}.").format(str(e.code))
|
||||
except URLError as e:
|
||||
msg_text = QCoreApplication.translate("changelog","Could not fetch update: {}.").format(str(e.reason))
|
||||
self.helper.gotChangelog.emit(msg_text)
|
||||
|
||||
class Helper(QObject):
|
||||
changelogFetched = Signal(str)
|
||||
gotChangelog = Signal(str)
|
||||
|
||||
def __init__(self, cwd: str, tmpfile: str):
|
||||
QObject.__init__(self)
|
||||
self.cwd = cwd
|
||||
self.tmpfile = tmpfile
|
||||
self.gotChangelog.connect(self.fetched)
|
||||
|
||||
def fetched(self, changelog: str):
|
||||
self.changelogFetched.emit(changelog)
|
||||
|
||||
@Slot(str, str)
|
||||
def write(self, filename, filedata):
|
||||
chdir(self.cwd)
|
||||
if path.exists(path.dirname(path.realpath(filename))):
|
||||
if filename.split(".")[-1] == "lpf":
|
||||
# Add header to file
|
||||
filedata = "LPFv1" + filedata
|
||||
f = open(path.realpath(filename), 'w', -1, 'utf8')
|
||||
f.write(filedata)
|
||||
f.close()
|
||||
chdir(path.dirname(path.realpath(__file__)))
|
||||
|
||||
@Slot(str, result=str)
|
||||
def load(self, filename):
|
||||
chdir(self.cwd)
|
||||
data = '{}'
|
||||
if path.exists(path.realpath(filename)):
|
||||
f = open(path.realpath(filename), 'r', -1, 'utf8')
|
||||
data = f.read()
|
||||
f.close()
|
||||
try:
|
||||
if data[:5] == "LPFv1":
|
||||
# V1 version of the file
|
||||
data = data[5:]
|
||||
elif data[0] == "{" and "type" in loads(data) and loads(data)["type"] == "logplotv1":
|
||||
pass
|
||||
elif data[:3] == "LPF":
|
||||
# More recent version of LogarithmPlotter file, but incompatible with the current format
|
||||
raise Exception(QCoreApplication.translate("This file was created by a more recent version of LogarithmPlotter and cannot be backloaded in LogarithmPlotter v{}.\nPlease update LogarithmPlotter to open this file.".format(__VERSION__)))
|
||||
else:
|
||||
raise Exception("Invalid LogarithmPlotter file.")
|
||||
except Exception as e: # If file can't be loaded
|
||||
QMessageBox.warning(None, 'LogarithmPlotter', QCoreApplication.translate('main','Could not open file "{}":\n{}').format(filename, e), QMessageBox.Ok) # Cannot parse file
|
||||
else:
|
||||
QMessageBox.warning(None, 'LogarithmPlotter', QCoreApplication.translate('main','Could not open file: "{}"\nFile does not exist.').format(filename), QMessageBox.Ok) # Cannot parse file
|
||||
try:
|
||||
chdir(path.dirname(path.realpath(__file__)))
|
||||
except NotADirectoryError as e:
|
||||
# Triggered on bundled versions of MacOS when it shouldn't. Prevents opening files.
|
||||
# See more at https://git.ad5001.eu/Ad5001/LogarithmPlotter/issues/1
|
||||
pass
|
||||
return data
|
||||
|
||||
@Slot(result=str)
|
||||
def gettmpfile(self):
|
||||
return self.tmpfile
|
||||
|
||||
@Slot()
|
||||
def copyImageToClipboard(self):
|
||||
clipboard = QApplication.clipboard()
|
||||
clipboard.setImage(QImage(self.tmpfile))
|
||||
|
||||
@Slot(result=str)
|
||||
def getVersion(self):
|
||||
return __VERSION__
|
||||
|
||||
@Slot(str, result=str)
|
||||
def getSetting(self, namespace):
|
||||
return config.getSetting(namespace)
|
||||
|
||||
@Slot(str, result=int)
|
||||
def getSettingInt(self, namespace):
|
||||
return config.getSetting(namespace)
|
||||
|
||||
@Slot(str, result=bool)
|
||||
def getSettingBool(self, namespace):
|
||||
return config.getSetting(namespace)
|
||||
|
||||
@Slot(str, str)
|
||||
def setSetting(self, namespace, value):
|
||||
return config.setSetting(namespace, value)
|
||||
|
||||
@Slot(str, bool)
|
||||
def setSettingBool(self, namespace, value):
|
||||
return config.setSetting(namespace, value)
|
||||
|
||||
@Slot(str, int)
|
||||
def setSettingInt(self, namespace, value):
|
||||
return config.setSetting(namespace, value)
|
||||
|
||||
@Slot(str)
|
||||
def setLanguage(self, new_lang):
|
||||
config.setSetting("language", new_lang)
|
||||
|
||||
@Slot(result=str)
|
||||
def getDebugInfos(self):
|
||||
"""
|
||||
Returns the version info about Qt, PySide6 & Python
|
||||
"""
|
||||
return QCoreApplication.translate('main',"Built with PySide6 (Qt) v{} and python v{}").format(PySide6_version, sys_version.split("\n")[0])
|
||||
|
||||
@Slot()
|
||||
def fetchChangelog(self):
|
||||
changelog_cache_path = path.join(path.dirname(path.realpath(__file__)), "CHANGELOG.md")
|
||||
print(changelog_cache_path)
|
||||
if path.exists(changelog_cache_path):
|
||||
# We have a cached version of the changelog, for env that don't have access to the internet.
|
||||
f = open(changelog_cache_path);
|
||||
self.changelogFetched.emit("".join(f.readlines()).strip())
|
||||
f.close()
|
||||
else:
|
||||
# Fetch it from the internet.
|
||||
runnable = ChangelogFetcher(self)
|
||||
QThreadPool.globalInstance().start(runnable)
|
||||
|
|
@ -1,195 +0,0 @@
|
|||
"""
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from PySide6.QtCore import QObject, Slot, Property, QCoreApplication
|
||||
from PySide6.QtGui import QImage, QColor
|
||||
from PySide6.QtWidgets import QApplication, QMessageBox
|
||||
|
||||
from os import path, remove
|
||||
from string import Template
|
||||
from tempfile import TemporaryDirectory
|
||||
from subprocess import Popen, TimeoutExpired, PIPE
|
||||
from platform import system
|
||||
from shutil import which
|
||||
from sys import argv
|
||||
|
||||
"""
|
||||
Searches for a valid Latex and DVIPNG (http://savannah.nongnu.org/projects/dvipng/)
|
||||
installation and collects the binary path in the DVIPNG_PATH variable.
|
||||
If not found, it will send an alert to the user.
|
||||
"""
|
||||
LATEX_PATH = which('latex')
|
||||
DVIPNG_PATH = which('dvipng')
|
||||
|
||||
DEFAULT_LATEX_DOC = Template(r"""
|
||||
\documentclass[]{minimal}
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage{calligra}
|
||||
\usepackage{amsfonts}
|
||||
|
||||
\title{}
|
||||
\author{}
|
||||
|
||||
\begin{document}
|
||||
|
||||
$$$$ $markup $$$$
|
||||
|
||||
\end{document}
|
||||
""")
|
||||
|
||||
class Latex(QObject):
|
||||
"""
|
||||
Base class to convert Latex equations into PNG images with custom font color and size.
|
||||
It doesn't have any python dependency, but requires a working latex installation and
|
||||
dvipng to be installed on the system.
|
||||
"""
|
||||
def __init__(self, tempdir: TemporaryDirectory):
|
||||
QObject.__init__(self)
|
||||
self.tempdir = tempdir
|
||||
|
||||
def check_latex_install(self):
|
||||
"""
|
||||
Checks if the current latex installation is valid.
|
||||
"""
|
||||
if LATEX_PATH is None:
|
||||
print("No Latex installation found.")
|
||||
if "--test-build" not in argv:
|
||||
QMessageBox.warning(None, "LogarithmPlotter - Latex setup", QCoreApplication.translate("latex", "No Latex installation found.\nIf you already have a latex distribution installed, make sure it's installed on your path.\nOtherwise, you can download a Latex distribution like TeX Live at https://tug.org/texlive/."))
|
||||
elif DVIPNG_PATH is None:
|
||||
print("DVIPNG not found.")
|
||||
if "--test-build" not in argv:
|
||||
QMessageBox.warning(None, "LogarithmPlotter - Latex setup", QCoreApplication.translate("latex", "DVIPNG was not found. Make sure you include it from your Latex distribution."))
|
||||
|
||||
@Property(bool)
|
||||
def latexSupported(self):
|
||||
return LATEX_PATH is not None and DVIPNG_PATH is not None
|
||||
|
||||
@Slot(str, float, QColor, result=str)
|
||||
def render(self, latex_markup: str, font_size: float, color: QColor) -> str:
|
||||
"""
|
||||
Prepares and renders a latex string into a png file.
|
||||
"""
|
||||
markup_hash = "render"+str(hash(latex_markup))
|
||||
export_path = path.join(self.tempdir.name, f'{markup_hash}_{int(font_size)}_{color.rgb()}')
|
||||
if self.latexSupported and not path.exists(export_path + ".png"):
|
||||
print("Rendering", latex_markup, export_path)
|
||||
# Generating file
|
||||
try:
|
||||
latex_path = path.join(self.tempdir.name, str(markup_hash))
|
||||
# If the formula is just recolored or the font is just changed, no need to recreate the DVI.
|
||||
if not path.exists(latex_path + ".dvi"):
|
||||
self.create_latex_doc(latex_path, latex_markup)
|
||||
self.convert_latex_to_dvi(latex_path)
|
||||
self.cleanup(latex_path)
|
||||
# Creating four pictures of different sizes to better handle dpi.
|
||||
self.convert_dvi_to_png(latex_path, export_path, font_size, color)
|
||||
# self.convert_dvi_to_png(latex_path, export_path+"@2", font_size*2, color)
|
||||
# self.convert_dvi_to_png(latex_path, export_path+"@3", font_size*3, color)
|
||||
# self.convert_dvi_to_png(latex_path, export_path+"@4", font_size*4, color)
|
||||
except Exception as e: # One of the processes failed. A message will be sent every time.
|
||||
raise e
|
||||
img = QImage(export_path);
|
||||
# Small hack, not very optimized since we load the image twice, but you can't pass a QImage to QML and expect it to be loaded
|
||||
return f'{export_path}.png,{img.width()},{img.height()}'
|
||||
|
||||
def create_latex_doc(self, export_path: str, latex_markup: str):
|
||||
"""
|
||||
Creates a temporary latex document with base file_hash as file name and a given expression markup latex_markup.
|
||||
"""
|
||||
ltx_path = export_path + ".tex"
|
||||
f = open(export_path + ".tex", 'w')
|
||||
f.write(DEFAULT_LATEX_DOC.substitute(markup = latex_markup))
|
||||
f.close()
|
||||
|
||||
def convert_latex_to_dvi(self, export_path: str):
|
||||
"""
|
||||
Converts a TEX file to a DVI file.
|
||||
"""
|
||||
self.run([
|
||||
LATEX_PATH,
|
||||
export_path + ".tex"
|
||||
])
|
||||
|
||||
def convert_dvi_to_png(self, dvi_path: str, export_path: str, font_size: float, color: QColor):
|
||||
"""
|
||||
Converts a DVI file to a PNG file.
|
||||
Documentation: https://linux.die.net/man/1/dvipng
|
||||
"""
|
||||
fg = color.convertTo(QColor.Rgb)
|
||||
fg = f'rgb {fg.redF()} {fg.greenF()} {fg.blueF()}'
|
||||
depth = int(font_size * 72.27 / 100) * 10
|
||||
self.run([
|
||||
DVIPNG_PATH,
|
||||
'-T', 'tight', # Make sure image borders are as tight around the equation as possible to avoid blank space.
|
||||
'--truecolor', # Make sure it's rendered in 24 bit colors.
|
||||
'-D',f'{depth}', # Depth of the image
|
||||
'-bg', 'Transparent', # Transparent background
|
||||
'-fg',f'{fg}', # Foreground of the wanted color.
|
||||
f'{dvi_path}.dvi', # Input file
|
||||
'-o',f'{export_path}.png', # Output file
|
||||
])
|
||||
|
||||
def run(self, process: list):
|
||||
"""
|
||||
Runs a subprocess and handles exceptions and messages them to the user.
|
||||
"""
|
||||
proc = Popen(process, stdout=PIPE, stderr=PIPE, cwd=self.tempdir.name)
|
||||
try:
|
||||
out, err = proc.communicate(timeout=2) # 2 seconds is already FAR too long.
|
||||
if proc.returncode != 0:
|
||||
# Process errored
|
||||
QMessageBox.warning(None, "LogarithmPlotter - Latex",
|
||||
QCoreApplication.translate("latex", "An exception occured within the creation of the latex formula.\nProcess '{}' ended with a non-zero return code {}:\n\n{}\nPlease make sure your latex installation is correct and report a bug if so.")
|
||||
.format(" ".join(process), proc.returncode, str(out, 'utf8')+"\n"+str(err,'utf8')))
|
||||
raise Exception(" ".join(process) + " process exited with return code " + str(proc.returncode) + ":\n" + str(out, 'utf8')+"\n"+str(err,'utf8'))
|
||||
except TimeoutExpired as e:
|
||||
# Process timed out
|
||||
proc.kill()
|
||||
out, err = proc.communicate()
|
||||
QMessageBox.warning(None, "LogarithmPlotter - Latex",
|
||||
QCoreApplication.translate("latex", "An exception occured within the creation of the latex formula.\nProcess '{}' took too long to finish:\n{}\nPlease make sure your latex installation is correct and report a bug if so.")
|
||||
.format(" ".join(process), str(out, 'utf8')+"\n"+str(err,'utf8')))
|
||||
raise Exception(" ".join(process) + " process timed out:\n" + str(out, 'utf8')+"\n"+str(err,'utf8'))
|
||||
|
||||
def cleanup(self, export_path):
|
||||
"""
|
||||
Removes auxiliary, logs and Tex temporary files.
|
||||
"""
|
||||
for i in [".tex", ".aux", ".log"]:
|
||||
remove(export_path + i)
|
||||
|
||||
"""
|
||||
@Slot(str, float, QColor, result=str)
|
||||
def render_legacy(self, latexstring, font_size, color = True):
|
||||
exprpath = path.join(self.tempdir.name, f'{hash(latexstring)}_{font_size}_{color.rgb()}.png')
|
||||
print("Rendering", latexstring, exprpath)
|
||||
if not path.exists(exprpath):
|
||||
fg = color.convertTo(QColor.Rgb)
|
||||
fg = f'rgb {fg.redF()} {fg.greenF()} {fg.blueF()}'
|
||||
preview('$${' + latexstring + '}$$', viewer='file', filename=exprpath, dvioptions=[
|
||||
"-T", "tight",
|
||||
"-z", "0",
|
||||
"--truecolor",
|
||||
f"-D {int(font_size * 72.27 / 100) * 10}", # See https://linux.die.net/man/1/dvipng#-D for convertion
|
||||
"-bg", "Transparent",
|
||||
"-fg", fg],
|
||||
euler=False)
|
||||
img = QImage(exprpath);
|
||||
# Small hack, not very optimized since we load the image twice, but you can't pass a QImage to QML and expect it to be loaded
|
||||
return f'{exprpath},{img.width()},{img.height()}'
|
||||
"""
|
101
README.md
|
@ -1,4 +1,5 @@
|
|||
#  LogarithmPlotter
|
||||
#  LogarithmPlotter
|
||||
|
||||
[](https://ci.ad5001.eu/Ad5001/LogarithmPlotter)
|
||||
[](https://hosted.weblate.org/engage/logarithmplotter/)
|
||||
[](https://flathub.org/apps/details/eu.ad5001.LogarithmPlotter)
|
||||
|
@ -7,56 +8,79 @@
|
|||
2D plotter software to make Bode plots, sequences and distribution functions.
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
You can find more screenshots on the [app website](https://apps.ad5001.eu/logarithmplotter/).
|
||||
You can find more screenshots on the [app's website](https://apps.ad5001.eu/logarithmplotter/).
|
||||
|
||||
## Run
|
||||
## Build & Run
|
||||
|
||||
You can simply run LogarithmPlotter using `python3 run.py`.
|
||||
First, you'll need to install all the required dependencies:
|
||||
|
||||
In order to test translations, you can use the `--lang=<lang code>` command line option to force the detected locale of LogarithmPlotter.
|
||||
- [Python 3](https://python.org) with [poetry](https://python-poetry.org/), setup a virtual environment, go to the `runtime-pyside6` directory, and call
|
||||
`poetry install`.
|
||||
- [npm](https://npmjs.com) (or [yarn](https://yarnpkg.com/)), go to the `common` directory, and run `npm install` (or `yarn install`).
|
||||
|
||||
You can simply run LogarithmPlotter using `python3 run.py`. It automatically compiles the language files (requires
|
||||
`pyside6-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`.
|
||||
|
||||
In order to test translations, you can use the `--lang=<lang code>` commandline option to force the locale.
|
||||
|
||||
## Install
|
||||
|
||||
### Generate installers:
|
||||
|
||||
All scripts noted here can be found in the `scripts` directory.
|
||||
|
||||
You can generate installers from LogarithmPlotter after installing all the dependencies:
|
||||
For all builds, you need [Python 3](https://python.org) with [PySide6](https://pypi.org/project/PySide6/) installable with `pip install PySide6`.
|
||||
- Windows installer:
|
||||
- You need `pyinstaller`. You can install it using `pip install pyinstaller`.
|
||||
- Run the `build-windows.bat` script (or `build-wine.sh` if you're cross-compiling with wine on Linux) to build an exe for LogarithmPlotter.
|
||||
- You also need [NSIS](https://nsis.sourceforge.io/Main_Page) (Linux users can install the [nsis](https://pkgs.org/download/nsis) package).
|
||||
- Run the `package-windows.bat` script (or `package-wine.sh`if you're cross-compiling on Linux). You will find a logarithmplotter-setup.exe installer in the dist/accountfree/ folder.
|
||||
- MacOS Archive creator installer:
|
||||
- You need `pyinstaller`. You can install it using `pip install pyinstaller`.
|
||||
- Run the `build-macosx.sh` script to build an .app for LogarithmPlotter which can be found in the dist directory.
|
||||
- Run the `package-macosx.sh` script. You will find a LogarithmPlotter-v0.1-dev-setup.dmg installer in the dist/ folder.
|
||||
You can generate installers for LogarithmPlotter after installing all the dependencies.
|
||||
|
||||
- Windows installer (crosscompiling from Linux):
|
||||
- Run `build-wine.sh` (requires wine) to build an exe for LogarithmPlotter in build/runtime-pyside6/dist.
|
||||
- You also need [NSIS](https://nsis.sourceforge.io/Main_Page) (the [nsis](https://pkgs.org/download/nsis) package is available on linux).
|
||||
- Run the `package-wine.sh` script. You will find a logarithmplotter-setup.exe installer in the build/runtime-pyside6/dist/logarithmplotter/ folder.
|
||||
- MacOS Archive creator installer:
|
||||
- Run the `build-macosx.sh` script to build an .app for LogarithmPlotter which can be found in the build/runtime-pyside6/dist directory.
|
||||
- Run the `package-macosx.sh` script. You will find a LogarithmPlotter-v<version>-setup.dmg installer in the
|
||||
build/runtime-pyside6/build/pysdist/ folder.
|
||||
- Linux packages:
|
||||
- To build a DEB, you need DPKG and stdeb. You can install the later by using `pip install stdeb`.
|
||||
- To build and install the flatpak, you need [flatpak-builder](https://docs.flatpak.org/en/latest/flatpak-builder.html) installed.
|
||||
- To build the snap, you need [snapcraft](https://snapcraft.io) installed.
|
||||
- Run `package-linux.sh`.
|
||||
|
||||
|
||||
### Linux
|
||||
|
||||
Run `bash linux/install_local.sh`
|
||||
- Run `package-deb.sh`. It will create an DSC and a DEB in build/runtime-pyside6/deb_dist/
|
||||
- Run `scripts/build.sh` followed by `snapcraft`. It .snap file in the root directory.
|
||||
- See [the flatpak repo](https://github.com/Ad5001/eu.ad5001.LogarithmPlotter) for instrutions on how to build the flatpak.
|
||||
|
||||
## Contribute
|
||||
|
||||
There are several ways to contribute to LogarithmPlotter.
|
||||
- You can help to translate [the project on Hosted Weblate](https://hosted.weblate.org/engage/logarithmplotter/):
|
||||
[](https://hosted.weblate.org/engage/logarithmplotter/)
|
||||
There are several ways you can contribute to LogarithmPlotter.
|
||||
|
||||
- You can help the development of LogarithmPlotter. In order to get started, take a look at the [wiki](https://git.ad5001.eu/Ad5001/LogarithmPlotter/wiki/_pages).
|
||||
- You can help to translate [the project on Hosted Weblate](https://hosted.weblate.org/engage/logarithmplotter/):
|
||||
[](https://hosted.weblate.org/engage/logarithmplotter/)
|
||||
|
||||
- You can help the development of LogarithmPlotter. In order to get started, take a look at
|
||||
the [wiki](https://git.ad5001.eu/Ad5001/LogarithmPlotter/wiki/_pages).
|
||||
|
||||
## Tests
|
||||
|
||||
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 `scripts/run-tests.sh`
|
||||
|
||||
## Legal notice
|
||||
LogarithmPlotter - 2D plotter software to make BODE plots, sequences and repartition functions.
|
||||
Copyright (C) 2021-2024 Ad5001 <mail@ad5001.eu>
|
||||
|
||||
LogarithmPlotter - 2D plotter software to make Bode plots, sequences and repartition functions.
|
||||
Copyright (C) 2021-2025 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
|
||||
|
@ -71,12 +95,19 @@ There are several ways to contribute to LogarithmPlotter.
|
|||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
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:
|
||||
- 🇳🇴 Norwegian translation by [Allan Nordhøy](https://github.com/comradekingu)
|
||||
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:
|
||||
|
||||
- 🇭🇺 Hungarian translation by [Óvári](https://github.com/ovari)
|
||||
- 🇳🇴 Norwegian translation by [Allan Nordhøy](https://github.com/comradekingu)
|
||||
- 🇪🇸 Spanish translation by gallegonovato and [IngrownMink4](https://github.com/IngrownMink4)
|
||||
|
||||
### Libraries used
|
||||
|
||||
LogarithmPlotter includes [expr-eval](https://github.com/silentmatt/expr-eval) a port of [ndef.parser](https://web.archive.org/web/20111023001618/http://www.undefined.ch/mparser/index.html) by Raphael Graf <r@undefined.ch>, ported to javascript by Matthew Crumley <email@matthewcrumley.com> (http://silentmatt.com/), and then to QMLJS by Ad5001.
|
||||
LogarithmPlotter includes [expr-eval](https://github.com/silentmatt/expr-eval) a port
|
||||
of [ndef.parser](https://web.archive.org/web/20111023001618/http://www.undefined.ch/mparser/index.html) by Raphael Graf
|
||||
<r@undefined.ch>, ported to javascript by Matthew Crumley
|
||||
<email@matthewcrumley.com> (http://silentmatt.com/), and then to QMLJS by Ad5001.
|
||||
|
||||
The specific file (LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/expr-eval.js) is licensed under the [MIT License](https://raw.githubusercontent.com/silentmatt/expr-eval/master/LICENSE.txt).
|
||||
All files in (common/src/lib/expr-eval/) except integration.mjs are licensed
|
||||
under the [MIT License](https://raw.githubusercontent.com/silentmatt/expr-eval/master/LICENSE.txt).
|
||||
|
|
1996
assets/i18n/lp_de.ts
Normal file
1996
assets/i18n/lp_en.ts
Normal file
1985
assets/i18n/lp_es.ts
Normal file
1999
assets/i18n/lp_fr.ts
Normal file
1996
assets/i18n/lp_hu.ts
Normal file
1836
assets/i18n/lp_nb_NO.ts
Normal file
1597
assets/i18n/lp_ta.ts
Normal file
1569
assets/i18n/lp_template.ts
Normal file
2
assets/i18n/release.sh
Executable file
|
@ -0,0 +1,2 @@
|
|||
#!/bin/bash
|
||||
pyside6-lrelease *.ts
|
65
assets/i18n/update.sh
Executable file
|
@ -0,0 +1,65 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# This file automatically renames .mjs files to js, and (tries) to fix most common ECMAScript
|
||||
# specificities so that lupdate doesn't cry out in pain.
|
||||
# See also: https://bugreports.qt.io/browse/QTBUG-123819
|
||||
#
|
||||
|
||||
escape() {
|
||||
str="$1"
|
||||
str="${str//\//\\/}" # Escape slashes
|
||||
str="${str//\*/\\*}" # Escape asterixes
|
||||
echo "$str"
|
||||
}
|
||||
|
||||
replace() {
|
||||
file="$1"
|
||||
from="$(escape "$2")"
|
||||
to="$(escape "$3")"
|
||||
sed -i "s/${from}/${to}/g" "$file"
|
||||
}
|
||||
|
||||
rm ../qml/eu/ad5001/LogarithmPlotter/js/index.mjs # Remove index which should not be scanned
|
||||
|
||||
files=$(find ../../common/src -name '*.mjs')
|
||||
for file in $files; do
|
||||
echo "Moving '$file' to '${file%.*}.js'..."
|
||||
mv "$file" "${file%.*}.js"
|
||||
# Replacements to make it valid js
|
||||
replace "${file%.*}.js" "^import" "/*import"
|
||||
replace "${file%.*}.js" "^export *" "/*export *"
|
||||
replace "${file%.*}.js" '.mjs"$' '.mjs"*/'
|
||||
replace "${file%.*}.js" "^export default" "/*export default*/"
|
||||
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
|
||||
# Updating locations in files
|
||||
for lp in *.ts; do
|
||||
echo "Replacing locations in $lp..."
|
||||
for file in $files; do
|
||||
replace "$lp" "${file%.*}.js" "$file"
|
||||
done
|
||||
done
|
||||
|
||||
for file in $files; do
|
||||
echo "Moving '${file%.*}.js' to '$file'..."
|
||||
mv "${file%.*}.js" "$file"
|
||||
# Resetting changes
|
||||
replace "$file" "/*await */" "await"
|
||||
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.#"
|
||||
done
|
Before Width: | Height: | Size: 198 B After Width: | Height: | Size: 198 B |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 289 B After Width: | Height: | Size: 289 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 620 B After Width: | Height: | Size: 620 B |
Before Width: | Height: | Size: 261 B After Width: | Height: | Size: 261 B |
1
assets/icons/common/manual.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd"><path d="M14 0v10l2-1.518 2 1.518v-10h4v24h-17c-1.657 0-3-1.343-3-3v-18c0-1.657 1.343-3 3-3h9zm6 20h-14.505c-1.375 0-1.375 2 0 2h14.505v-2z"/></svg>
|
After Width: | Height: | Size: 251 B |
1
assets/icons/common/new.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd"><path d="M12 0l-2.138 2.63-3.068-1.441-.787 3.297-3.389.032.722 3.312-3.039 1.5 2.088 2.671-2.088 2.67 3.039 1.499-.722 3.312 3.389.033.787 3.296 3.068-1.441 2.138 2.63 2.139-2.63 3.068 1.441.786-3.296 3.39-.033-.722-3.312 3.038-1.499-2.087-2.67 2.087-2.671-3.038-1.5.722-3.312-3.39-.032-.786-3.297-3.068 1.441-2.139-2.63zm0 15.5c.69 0 1.25.56 1.25 1.25s-.56 1.25-1.25 1.25-1.25-.56-1.25-1.25.56-1.25 1.25-1.25zm1-1.038v-7.462h-2v7.462h2z"/></svg>
|
After Width: | Height: | Size: 550 B |
Before Width: | Height: | Size: 273 B After Width: | Height: | Size: 273 B |
Before Width: | Height: | Size: 696 B After Width: | Height: | Size: 696 B |
Before Width: | Height: | Size: 270 B After Width: | Height: | Size: 270 B |
Before Width: | Height: | Size: 370 B After Width: | Height: | Size: 370 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
1
assets/icons/history/delete.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../common/close.svg
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
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/properties/displayMode.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../common/appearance.svg
|
1
assets/icons/properties/displayStyle.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/properties/labelPosition.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../common/arrow.svg
|
1
assets/icons/properties/labelX.svg
Symbolic link
|
@ -0,0 +1 @@
|
|||
../common/position.svg
|