Compare commits

..

4 commits

Author SHA1 Message Date
Ad5001 64d5f11ff9
FlatPlusOperation for future simplifications!
All checks were successful
continuous-integration/drone/push Build is passing
2022-10-25 16:26:08 +02:00
Ad5001 80d0dad63a
Implementing parseBinaryOperator
All checks were successful
continuous-integration/drone/push Build is passing
2022-10-23 18:09:20 +02:00
Ad5001 d7704110dd
Lots of changes to the AST Builder.
All checks were successful
continuous-integration/drone/push Build is passing
Adding IDENTIFIER Token type with optional differentiation between functions, constants and variables for syntax highlighting.
Adding substitute for many AST elements.
Properly starting builder: parsing identifiers, functions, variables, array values, properties, numbers, strings and subexpressions.
2022-10-23 15:43:47 +02:00
Ad5001 666d611e95
Fixing up old stuff, cleaning up and standardizing AST. 2022-10-22 14:06:03 +02:00
152 changed files with 8997 additions and 9796 deletions

1
.gitignore vendored
View file

@ -16,7 +16,6 @@ linux/flatpak/.flatpak-builder
*.jsc *.jsc
*.qmlc *.qmlc
*.log *.log
**/*.dxvk-cache
.DS_Store .DS_Store
**/.DS_Store **/.DS_Store
**/__pycache__/ **/__pycache__/

3
.gitmodules vendored
View file

@ -1,3 +1,6 @@
[submodule "LogarithmPlotter/qml/eu/ad5001/MixedMenu"] [submodule "LogarithmPlotter/qml/eu/ad5001/MixedMenu"]
path = LogarithmPlotter/qml/eu/ad5001/MixedMenu path = LogarithmPlotter/qml/eu/ad5001/MixedMenu
url = https://git.ad5001.eu/Ad5001/MixedMenu url = https://git.ad5001.eu/Ad5001/MixedMenu
[submodule "linux/flatpak"]
path = linux/flatpak
url = https://github.com/Ad5001/eu.ad5001.LogarithmPlotter

View file

@ -1,123 +1,5 @@
# Changelog # Changelog
## v0.5.0 (11 Jan 2024)
**New**
* New, reworked application icon.
* Graph is now mouse interactive:
* You can now drag to move and scroll to zoom!
* Builtin functions now provide usage when used in the autocomplete of the expression editor.
**Changes**
* When creating an object that can be positioned, new default behavior is to pick first instead of opening object settings.
* Icons with text now use the SVG's text element, allowing them to integrate better with the system's default font.
* Special characters popup is now context aware (e.g. no sub/supscript symbols in expressions).
* New symbols in special characters popup.
* Integrals and derivatives can now be provided with an executable object (e.g. Functions) instead of strings as function.
* New description on Linux.
**Fixed bugs**
* Fixing ∞ 'variable' in domains and expressions.
* Several other bugs related to constants in expresions were fixed as well.
* Builtin functions now send an error message when not provided with the proper arguments.
**Internal changes**
* Updated to PySide6 v6.6.1.
* Reworked continuous functions' rendering to make it faster.
* Removed old bits from an unfinished new parser that weren't used.
## v0.4.0 (27 May 2023)
**Changes**
* Fully ported to PySide6 (Qt6).
* Greet screen settings are now scrollable.
* Changelog is now freezed to current version.
**New**
* Customizable color schemes for expressions.
* New, rewamped and improved picked location overlay settings:
* It's now possible to disable picking x or y when setting a location.
* Properties which are related to positioning (X, Y, Label's X position) can now be set using the picker.
* Visual redesign that enhances readability of settings.
* There is now a button to hide picker settings.
**Fixed bugs**
* Cursors in expression are now easier to see.
* Symbols in LaTeX rendered Texts cause the LaTeX renderer to crash.
* Underscores in distribution names are automatically removed if the name is modified.
* Autocomplete categories now properly respect theme colors.
* Functions in expressions (like indexOf, map...) now properly send errors when the arguments are of the wrong type or count.
* Executable Objects called (like functions, bode magnitures, phases...) now send an error if provided with no arguments.
* Function calls with no argument no longer make LogarithmPlotter crash under certain circumstances.
* Thank you dialog's lists are no longer draggable.
**Internal changes**
* A lot of inner changes led by porting to Qt6, fixing a lot of bugs at the same time.
* Disabled auto detect of visual theme if the `QT_QUICK_CONTROLS_STYLE` environment variable is set.
* (macOS, Windows, Flatpak) Drastically reducing installer sizes (more than halved).
* (Launchpad/Ubuntu) Using custom built packages of PySide6, meaning smaller installation and distro dependency.
## v0.3.0 (28 Oct 2022)
**New**
* New completely revamped expression editor:
* Automatic closing of parentheses and brackets (can be disabled in settings).
* Syntax highlighting (can be disabled in the settings).
* Autocompletion is now available (for functions, variables and constants, object names and properties) (can be disabled in the settings).
* Object properties can now be used in expressions (e.g. if you have a point named A, you can use A.x to access its x value).
* Executable objects can be now be used in expressions (e.g. if you have a function named 'f', it's accessible using `f(<value>)`).
* LaTeX-rendered formulas are now used in the Objects and History tabs when LaTeX rendering is enabled.
* Errors in formulas are now reported in message boxes.
**Changes**
* The Object Editor dialog has been completely reworked internally, resulting in notable performance improvements.
* Vast improvements to the objects system: names can no longer be shared amongst different objects.
* Disabled access to custom variable and function definition in expressions (can cause issues and vulnerabilities)
* When using the set position cursor, the position change is now saved a single history action.
* Distribution are now prefixed with an 'F_' to prevent confusion with X Cursors.
**Added translations**
* Autocompletion categories (English, French, German, Hungarian).
* Expression editor settings (English, French, German, Hungarian).
* Expression syntax errors (English, French, German, Hungarian).
* On top of the above:
* Hungarian: v0.2.0 added text (thanks @ovari!)
* Spanish: Menu bars (thanks @Sergio Varela)
* You can contribute to translation on [Weblate](https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/#repository).
**Fixed bugs**
* Fixing Texts not being properly recognized as texts when saving.
* Text's 'Disable LaTeX' property is now properly saved.
* X Cursors LaTeX rendering made the app crash.
* Attempting to insert special character no longer automatically saves the expression you're editing.
* Proper HDPI support for icons and buttons (note: HDPI is not available for the rendered canvas yet).
* Support for non-latin characters in variables (e.g. greek letters, subtext, suptext)
* Silent error when misentering variable names in the expression editor causing internal issues.
* Fixing some utils function simplifying parentheses when they shouldn't have.
* (flatpak and KDE SDK) Fixing the sometimes invisible buttons on the objects tab on startup.
* (macos) Application string version does not match LogarithmPlotter's version.
* (debian) (Normally) Fixing deb building.
**Internal changes**
* Object dependencies are now registered on both the dependant object, and the object it's depending on.
* Objects now have a proper per-name registry.
* Object Editor Dialog has been reworked to use loaders insteads.
* Reworked the file loading system to be able to load dependencies properly.
## v0.2.0 (22 Apr 2022) ## v0.2.0 (22 Apr 2022)
**New** **New**

View file

@ -1,6 +1,6 @@
""" """
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -17,7 +17,7 @@
""" """
from shutil import which from shutil import which
__VERSION__ = "0.6.0" __VERSION__ = "0.3.0"
is_release = False is_release = False

View file

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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,2 +1,2 @@
#!/bin/bash #!/bin/bash
lupdate -extensions mjs,js,qs,qml,py -recursive .. -ts lp_*.ts lupdate -extensions js,qs,qml,py -recursive .. -ts lp_*.ts

View file

@ -1,6 +1,6 @@
""" """
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -18,10 +18,10 @@
from time import time from time import time
from PySide6.QtWidgets import QApplication from PySide2.QtWidgets import QApplication
from PySide6.QtQml import QQmlApplicationEngine from PySide2.QtQml import QQmlApplicationEngine
from PySide6.QtCore import Qt, QTranslator, QLocale from PySide2.QtCore import Qt, QTranslator, QLocale
from PySide6.QtGui import QIcon from PySide2.QtGui import QIcon
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
from os import getcwd, chdir, environ, path, remove, close from os import getcwd, chdir, environ, path, remove, close
@ -41,42 +41,40 @@ chdir(path.dirname(path.realpath(__file__)))
if path.realpath(path.join(getcwd(), "..")) not in sys_path: if path.realpath(path.join(getcwd(), "..")) not in sys_path:
sys_path.append(path.realpath(path.join(getcwd(), ".."))) sys_path.append(path.realpath(path.join(getcwd(), "..")))
from LogarithmPlotter import __VERSION__ from LogarithmPlotter import __VERSION__
from LogarithmPlotter.util import config, native from LogarithmPlotter.util import config, native
from LogarithmPlotter.util.update import check_for_updates from LogarithmPlotter.util.update import check_for_updates
from LogarithmPlotter.util.helper import Helper from LogarithmPlotter.util.helper import Helper
from LogarithmPlotter.util.latex import Latex from LogarithmPlotter.util.latex import Latex
from LogarithmPlotter.util.js import PyJSValue
config.init() config.init()
def get_linux_theme(): def get_linux_theme():
des = { des = {
"KDE": "Fusion", "KDE": "org.kde.desktop",
"gnome": "Basic", "gnome": "default",
"lxqt": "Fusion", "lxqt": "fusion",
"mate": "Fusion", "mate": "fusion",
} }
if "XDG_SESSION_DESKTOP" in environ: if "XDG_SESSION_DESKTOP" in environ:
return des[environ["XDG_SESSION_DESKTOP"]] if environ["XDG_SESSION_DESKTOP"] in des else "Fusion" return des[environ["XDG_SESSION_DESKTOP"]] if environ["XDG_SESSION_DESKTOP"] in des else "fusion"
else: else:
# Android # Android
return "Material" return "Material"
def run(): def run():
if not 'QT_QUICK_CONTROLS_STYLE' in environ:
environ["QT_QUICK_CONTROLS_STYLE"] = { environ["QT_QUICK_CONTROLS_STYLE"] = {
"linux": get_linux_theme(), "linux": get_linux_theme(),
"freebsd": get_linux_theme(), "freebsd": get_linux_theme(),
"win32": "Universal" if os_release == "10" else "Fusion", "win32": "universal" if os_release == "10" else "fusion",
"cygwin": "Fusion", "cygwin": "fusion",
"darwin": "macOS" "darwin": "default"
}[platform] }[platform]
dep_time = time() dep_time = time()
print("Loaded dependencies in " + str((dep_time - start_time) * 1000) + "ms.") print("Loaded dependencies in " + str((dep_time - start_time)*1000) + "ms.")
icon_fallbacks = QIcon.fallbackSearchPaths(); icon_fallbacks = QIcon.fallbackSearchPaths();
base_icon_path = path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "icons") base_icon_path = path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "icons")
@ -86,45 +84,42 @@ def run():
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "settings"))) icon_fallbacks.append(path.realpath(path.join(base_icon_path, "settings")))
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "settings", "custom"))) icon_fallbacks.append(path.realpath(path.join(base_icon_path, "settings", "custom")))
QIcon.setFallbackSearchPaths(icon_fallbacks); QIcon.setFallbackSearchPaths(icon_fallbacks);
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
app = QApplication(argv) app = QApplication(argv)
app.setApplicationName("LogarithmPlotter") app.setApplicationName("LogarithmPlotter")
app.setDesktopFileName("eu.ad5001.LogarithmPlotter.desktop")
app.setOrganizationName("Ad5001") app.setOrganizationName("Ad5001")
app.styleHints().setShowShortcutsInContextMenus(True) app.styleHints().setShowShortcutsInContextMenus(True)
app.setWindowIcon(QIcon(path.realpath(path.join(getcwd(), "logarithmplotter.svg")))) app.setWindowIcon(QIcon(path.realpath(path.join(getcwd(), "logarithmplotter.svg"))))
# Installing translators # Installing translators
translator = QTranslator() translator = QTranslator()
# Check if lang is forced. # Check if lang is forced.
forcedlang = [p for p in argv if p[:7] == "--lang="] forcedlang = [p for p in argv if p[:7]=="--lang="]
locale = QLocale(forcedlang[0][7:]) if len(forcedlang) > 0 else QLocale() locale = QLocale(forcedlang[0][7:]) if len(forcedlang) > 0 else QLocale()
if translator.load(locale, "lp", "_", path.realpath(path.join(getcwd(), "i18n"))): if (translator.load(locale, "lp", "_", path.realpath(path.join(getcwd(), "i18n")))):
app.installTranslator(translator); app.installTranslator(translator);
# Installing macOS file handler. # Installing macOS file handler.
macOSFileOpenHandler = None macOSFileOpenHandler = None
if platform == "darwin": if platform == "darwin":
macOSFileOpenHandler = native.MacOSFileOpenHandler() macOSFileOpenHandler = native.MacOSFileOpenHandler()
app.installEventFilter(macOSFileOpenHandler) app.installEventFilter(macOSFileOpenHandler)
engine = QQmlApplicationEngine() engine = QQmlApplicationEngine()
global tmpfile global tmpfile
helper = Helper(pwd, tmpfile) helper = Helper(pwd, tmpfile)
latex = Latex(tempdir) latex = Latex(tempdir)
js_globals = PyJSValue(engine.globalObject()) engine.rootContext().setContextProperty("Helper", helper)
js_globals.Modules = engine.newObject() engine.rootContext().setContextProperty("Latex", latex)
js_globals.Helper = engine.newQObject(helper)
js_globals.Latex = engine.newQObject(latex)
engine.rootContext().setContextProperty("TestBuild", "--test-build" in argv) engine.rootContext().setContextProperty("TestBuild", "--test-build" in argv)
engine.rootContext().setContextProperty("StartTime", dep_time) engine.rootContext().setContextProperty("StartTime", dep_time)
app.translate("About", "About LogarithmPlotter") app.translate("About","About LogarithmPlotter") # FOR SOME REASON, if this isn't included, Qt refuses to load the QML file.
# FOR SOME REASON, if this isn't included, Qt refuses to load the QML file.
engine.addImportPath(path.realpath(path.join(getcwd(), "qml"))) engine.addImportPath(path.realpath(path.join(getcwd(), "qml")))
engine.load(path.realpath(path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "LogarithmPlotter.qml"))) engine.load(path.realpath(path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "LogarithmPlotter.qml")))
if not engine.rootObjects(): if not engine.rootObjects():
print("No root object", path.realpath(path.join(getcwd(), "qml"))) print("No root object", path.realpath(path.join(getcwd(), "qml")))
@ -134,26 +129,26 @@ def run():
# Open the current diagram # Open the current diagram
chdir(pwd) chdir(pwd)
if len(argv) > 0 and path.exists(argv[-1]) and argv[-1].split('.')[-1] in ['lpf']: if len(argv) > 0 and path.exists(argv[-1]) and argv[-1].split('.')[-1] in ['lpf']:
js_globals.Modules.IO.loadDiagram(argv[-1]) engine.rootObjects()[0].loadDiagram(argv[-1])
chdir(path.dirname(path.realpath(__file__))) chdir(path.dirname(path.realpath(__file__)))
if platform == "darwin": if platform == "darwin":
macOSFileOpenHandler.init_io(js_globals.Modules.IO) macOSFileOpenHandler.init_graphics(engine.rootObjects()[0])
# Check for LaTeX installation if LaTeX support is enabled # Check for LaTeX installation if LaTeX support is enabled
if config.getSetting("enable_latex"): if config.getSetting("enable_latex"):
latex.check_latex_install() latex.check_latex_install()
# Check for updates # Check for updates
if config.getSetting("check_for_updates"): if config.getSetting("check_for_updates"):
check_for_updates(__VERSION__, engine.rootObjects()[0]) check_for_updates(__VERSION__, engine.rootObjects()[0])
exit_code = app.exec() exit_code = app.exec_()
tempdir.cleanup() tempdir.cleanup()
config.save() config.save()
exit(exit_code) exit(exit_code)
if __name__ == "__main__": if __name__ == "__main__":
run() run()

View file

@ -1,64 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg id="SVGRoot" width="48" height="48" version="1.1" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<svg <title>LogarithmPlotter Icon v1.0</title>
width="24.0px" <g fill-rule="evenodd" stroke-width="2">
height="24.0px" <rect width="48" height="48" ry="6" fill="#fff"/>
viewBox="0 0 24.0 24.0" <rect x="2" y="38" width="44" height="4" stroke-opacity="0"/>
version="1.1" <rect x="18" y="2" width="4" height="44" stroke-opacity="0"/>
id="SVGRoot" </g>
xml:space="preserve" <path d="M 42.05,2 A 40.05,36.05 0 0 1 2,38.05" fill="none" stroke="#f00" stroke-width="3.9012"/>
xmlns="http://www.w3.org/2000/svg" </svg>
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><title
id="title836">LogarithmPlotter Icon v1.0</title><defs
id="defs833" /><metadata
id="metadata836"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title>LogarithmPlotter Icon v1.0</dc:title><cc:license
rdf:resource="http://creativecommons.org/licenses/by-nc-sa/4.0/" /><dc:date>2021</dc:date><dc:creator><cc:Agent><dc:title>Ad5001</dc:title></cc:Agent></dc:creator><dc:rights><cc:Agent><dc:title>(c) Ad5001 2021 - All rights reserved</dc:title></cc:Agent></dc:rights></cc:Work><cc:License
rdf:about="http://creativecommons.org/licenses/by-nc-sa/4.0/"><cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" /><cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" /><cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" /><cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" /><cc:prohibits
rdf:resource="http://creativecommons.org/ns#CommercialUse" /><cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /><cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" /></cc:License></rdf:RDF></metadata><g
id="layer2"
transform="matrix(1,0,0,0.94444444,0,1.1666667)"
style="fill:#666666"><rect
style="fill:#666666;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect1546"
width="18"
height="18"
x="3"
y="3"
ry="2.25" /></g><g
id="layer2-6"
transform="matrix(1,0,0,0.94444444,0,0.16666668)"
style="fill:#f9f9f9"><rect
style="fill:#f9f9f9;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect1546-7"
width="18"
height="18"
x="3"
y="3"
ry="2.25" /></g><g
id="layer1"
style="stroke-width:2;stroke-dasharray:none"><rect
style="fill:#000000;fill-rule:evenodd;stroke-width:1.86898;stroke-dasharray:none;stroke-opacity:0"
id="rect1410"
width="14"
height="2"
x="5"
y="15.5" /><rect
style="fill:#000000;fill-rule:evenodd;stroke-width:2;stroke-dasharray:none;stroke-opacity:0"
id="rect1412"
width="2"
height="15"
x="9"
y="3.9768662" /><path
style="fill:none;fill-rule:evenodd;stroke:#ff0000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path1529"
d="M 18,4 C 18,10.017307 13.40948,15.5 5,15.5" /></g></svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 488 B

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,11 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick import QtQuick 2.12
import Qt.labs.platform as Native import QtQuick.Dialogs 1.3
//import QtQuick.Controls 2.15 //import QtQuick.Controls 2.15
import eu.ad5001.MixedMenu 1.1 import eu.ad5001.MixedMenu 1.1
import "js/historylib.mjs" as HistoryLib import "js/objects.js" as Objects
import "js/historylib.js" as HistoryLib
import "js/math/latex.js" as LatexJS
/*! /*!
@ -33,6 +35,7 @@ import "js/historylib.mjs" as HistoryLib
\sa LogarithmPlotter \sa LogarithmPlotter
*/ */
MenuBar { MenuBar {
property var settings: settingsMenu
Menu { Menu {
title: qsTr("&File") title: qsTr("&File")
@ -89,36 +92,30 @@ MenuBar {
icon.color: enabled ? sysPalette.windowText : sysPaletteIn.windowText icon.color: enabled ? sysPalette.windowText : sysPaletteIn.windowText
enabled: history.redoCount > 0 enabled: history.redoCount > 0
} }
MenuSeparator { }
Action { Action {
text: qsTr("&Copy plot") text: qsTr("&Copy plot")
shortcut: StandardKey.Copy shortcut: StandardKey.Copy
onTriggered: root.copyDiagramToClipboard() onTriggered: root.copyDiagramToClipboard()
icon.name: 'edit-copy' icon.name: 'edit-copy'
} }
MenuSeparator { }
Action {
text: qsTr("&Preferences")
shortcut: StandardKey.Copy
onTriggered: preferences.open()
icon.name: 'settings'
}
} }
Menu { Menu {
title: qsTr("&Create") title: qsTr("&Create")
// Services repeater // Services repeater
Repeater { Repeater {
model: Object.keys(Modules.Objects.types) model: Object.keys(Objects.types)
MenuItem { MenuItem {
text: Modules.Objects.types[modelData].displayType() text: Objects.types[modelData].displayType()
visible: Modules.Objects.types[modelData].createable() visible: Objects.types[modelData].createable()
height: visible ? implicitHeight : 0 height: visible ? implicitHeight : 0
icon.name: modelData icon.name: modelData
icon.source: './icons/objects/' + modelData + '.svg' icon.source: './icons/objects/' + modelData + '.svg'
icon.color: sysPalette.buttonText icon.color: sysPalette.buttonText
onTriggered: { onTriggered: {
var newObj = Modules.Objects.createNewRegisteredObject(modelData) var newObj = Objects.createNewRegisteredObject(modelData)
history.addToHistory(new HistoryLib.CreateNewObject(newObj.name, modelData, newObj.export())) history.addToHistory(new HistoryLib.CreateNewObject(newObj.name, modelData, newObj.export()))
objectLists.update() objectLists.update()
} }
@ -126,6 +123,78 @@ MenuBar {
} }
} }
Menu {
id: settingsMenu
title: qsTr("&Settings")
Action {
id: checkForUpdatesMenuSetting
text: qsTr("Check for updates on startup")
checkable: true
checked: Helper.getSettingBool("check_for_updates")
onTriggered: Helper.setSettingBool("check_for_updates", checked)
icon.name: 'update'
}
Action {
id: resetRedoStackMenuSetting
text: qsTr("Reset redo stack automaticly")
checkable: true
checked: Helper.getSettingBool("reset_redo_stack")
onTriggered: Helper.setSettingBool("reset_redo_stack", checked)
icon.name: 'timeline'
}
Action {
id: enableLatexJSSetting
text: qsTr("Enable LaTeX rendering")
checkable: true
checked: Helper.getSettingBool("enable_latex")
onTriggered: {
Helper.setSettingBool("enable_latex", checked)
LatexJS.enabled = checked
drawCanvas.requestPaint()
}
icon.name: 'Expression'
}
Menu {
title: qsTr("Expression editor")
Action {
id: autocloseFormulaSetting
text: qsTr("Automatically close parenthesises and brackets")
checkable: true
checked: Helper.getSettingBool("expression_editor.autoclose")
onTriggered: {
Helper.setSettingBool("expression_editor.autoclose", checked)
}
icon.name: 'Text'
}
Action {
id: colorizeFormulaSetting
text: qsTr("Enable syntax highlighting")
checkable: true
checked: Helper.getSettingBool("expression_editor.colorize")
onTriggered: {
Helper.setSettingBool("expression_editor.colorize", checked)
}
icon.name: 'appearance'
}
Action {
id: autocompleteFormulaSetting
text: qsTr("Enable autocompletion")
checkable: true
checked: Helper.getSettingBool("autocompletion.enabled")
onTriggered: {
Helper.setSettingBool("autocompletion.enabled", checked)
}
icon.name: 'label'
}
}
}
Menu { Menu {
title: qsTr("&Help") title: qsTr("&Help")
Action { Action {
@ -167,17 +236,16 @@ MenuBar {
} }
} }
Native.MessageDialog { MessageDialog {
id: saveUnsavedChangesDialog id: saveUnsavedChangesDialog
title: qsTr("Save unsaved changes?") title: qsTr("Save unsaved changes?")
icon: StandardIcon.Question
text: qsTr("This plot contains unsaved changes. By doing this, all unsaved data will be lost. Continue?") text: qsTr("This plot contains unsaved changes. By doing this, all unsaved data will be lost. Continue?")
buttons: Native.MessageDialog.Save | Native.MessageDialog.Discard | Native.MessageDialog.Cancel standardButtons: StandardButton.Yes | StandardButton.No
onYes: Qt.quit()
onSaveClicked: settings.save()
onDiscardClicked: Qt.quit()
} }
function openSaveUnsavedChangesDialog() { function showSaveUnsavedChangesDialog() {
saveUnsavedChangesDialog.open() saveUnsavedChangesDialog.visible = true
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,10 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick import QtQuick 2.12
import QtQml import QtQml 2.12
import QtQuick.Window import "../js/objects.js" as Objects
import "../js/historylib.mjs" as HistoryLib import "../js/historylib.js" as HistoryLib
import "../js/history/common.js" as HistoryCommon
/*! /*!
\qmltype History \qmltype History
@ -212,8 +213,7 @@ Item {
} }
Component.onCompleted: { Component.onCompleted: {
Modules.History.history = historyObj HistoryLib.history = historyObj
Modules.History.themeTextColor = sysPalette.windowText HistoryCommon.themeTextColor = sysPalette.windowText
Modules.History.imageDepth = Screen.devicePixelRatio
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,10 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick.Controls import QtQuick.Controls 2.12
import QtQuick import QtQuick 2.12
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import "../js/utils.mjs" as Utils import "../js/utils.js" as Utils
/*! /*!
@ -54,7 +54,6 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
placeholderText: qsTr("Filter...") placeholderText: qsTr("Filter...")
category: "all"
} }
ScrollView { ScrollView {

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,10 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick.Controls import QtQuick.Controls 2.12
import QtQuick import QtQuick 2.12
import Qt5Compat.GraphicalEffects import QtGraphicalEffects 1.15
import "../js/utils.mjs" as Utils import "../js/utils.js" as Utils
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
@ -132,6 +132,8 @@ Button {
color: sysPalette.windowText color: sysPalette.windowText
} }
//text: content
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.delay: 200 ToolTip.delay: 200
ToolTip.text: content ToolTip.text: content

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,10 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick import QtQuick 2.12
import Qt.labs.platform as Native import "js/objects.js" as Objects
import "js/utils.mjs" as Utils import "js/utils.js" as Utils
import "js/mathlib.mjs" as MathLib import "js/mathlib.js" as MathLib
/*! /*!
\qmltype LogGraphCanvas \qmltype LogGraphCanvas
@ -126,6 +126,37 @@ Canvas {
*/ */
property int maxgradx: 20 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 \qmlproperty var LogGraphCanvas::imageLoaders
Dictionary of format {image: [callback.image data]} containing data for defered image loading. Dictionary of format {image: [callback.image data]} containing data for defered image loading.
@ -137,25 +168,25 @@ Canvas {
*/ */
property var ctx property var ctx
Component.onCompleted: { Component.onCompleted: imageLoaders = {}
imageLoaders = {}
Modules.Canvas.initialize(canvas, drawingErrorDialog)
}
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) { onPaint: function(rect) {
//console.log('Redrawing') //console.log('Redrawing')
if(rect.width == canvas.width) { // Redraw full canvas if(rect.width == canvas.width) { // Redraw full canvas
Modules.Canvas.redraw() 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) obj.draw(canvas, ctx)
}
}
ctx.lineWidth = 1
} }
} }
@ -169,7 +200,172 @@ Canvas {
}) })
} }
/*! /*!
\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*(y==0)))
}
} else {
for(var x = 1; x < drawMaxX; x += 1) {
var drawX = x*xaxisstep1
var txtX = xaxisstepExpr.simplify(x)
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)
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(visible(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(visible(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) \qmlmethod double LogGraphCanvas::x2px(double x)
Converts an \c x coordinate to it's relative position on the canvas. 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. It supports both logarithmic and non logarithmic scale depending on the currently selected mode.
@ -209,4 +405,71 @@ Canvas {
function px2y(px) { function px2y(px) {
return -(px/yzoom-ymax) return -(px/yzoom-ymax)
} }
/*!
\qmlmethod bool LogGraphCanvas::visible(double x, double y)
Checks whether a plot point (\c x, \c y) is visible or not on the canvas.
*/
function visible(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)
}
}
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,15 +16,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQml import QtQml 2.12
import QtQuick.Controls import QtQuick.Controls 2.12
import eu.ad5001.MixedMenu 1.1 import eu.ad5001.MixedMenu 1.1
import QtQuick.Layouts 1.12 import QtQuick.Layouts 1.12
import QtQuick import QtQuick 2.12
// Auto loading all objects.
// Auto loading all modules. import "js/objs/autoload.js" as ALObjects
import "js/autoload.js" as ModulesAutoload
import "js/objects.js" as Objects
import "js/math/latex.js" as LatexJS
import eu.ad5001.LogarithmPlotter.History 1.0 import eu.ad5001.LogarithmPlotter.History 1.0
import eu.ad5001.LogarithmPlotter.ObjectLists 1.0 import eu.ad5001.LogarithmPlotter.ObjectLists 1.0
import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup
@ -44,7 +45,16 @@ ApplicationWindow {
color: sysPalette.window color: sysPalette.window
title: "LogarithmPlotter " + (settings.saveFilename != "" ? " - " + settings.saveFilename.split('/').pop() : "") + (history.saved ? "" : "*") title: "LogarithmPlotter " + (settings.saveFilename != "" ? " - " + settings.saveFilename.split('/').pop() : "") + (history.saved ? "" : "*")
SystemPalette { id: sysPalette; colorGroup: SystemPalette.Active } 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 } SystemPalette { id: sysPaletteIn; colorGroup: SystemPalette.Disabled }
menuBar: appMenu.trueItem menuBar: appMenu.trueItem
@ -55,8 +65,6 @@ ApplicationWindow {
Popup.GreetScreen {} Popup.GreetScreen {}
Popup.Preferences {id: preferences}
Popup.Changelog {id: changelog} Popup.Changelog {id: changelog}
Popup.About {id: about} Popup.About {id: about}
@ -161,7 +169,7 @@ ApplicationWindow {
property bool firstDrawDone: false property bool firstDrawDone: false
onPainted: if(!firstDrawDone) { onPainted: if(!firstDrawDone) {
firstDrawDone = true; firstDrawDone = true;
console.info("First paint done in " + (new Date().getTime()-(StartTime*1000)) + "ms") console.info("First paint done in " + (new Date().getTime()-(StartTime*1000)) + "ms")
if(TestBuild == true) { if(TestBuild == true) {
@ -170,13 +178,6 @@ ApplicationWindow {
} }
} }
ViewPositionChangeOverlay {
id: viewPositionChanger
anchors.fill: parent
canvas: parent
settingsInstance: settings
}
PickLocationOverlay { PickLocationOverlay {
id: positionPicker id: positionPicker
anchors.fill: parent anchors.fill: parent
@ -184,6 +185,130 @@ ApplicationWindow {
} }
} }
/*!
\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 { Timer {
id: delayRefreshTimer id: delayRefreshTimer
repeat: false repeat: false
@ -198,26 +323,10 @@ ApplicationWindow {
onTriggered: Qt.quit() // Quit after paint on test build onTriggered: Qt.quit() // Quit after paint on test build
} }
onClosing: function(close) { onClosing: {
if(!history.saved) { if(!history.saved) {
close.accepted = false close.accepted = false
appMenu.openSaveUnsavedChangesDialog() appMenu.showSaveUnsavedChangesDialog()
}
}
/*!
\qmlmethod void LogarithmPlotter::updateObjectsLists()
Updates the objects lists when loading a file.
*/
function updateObjectsLists() {
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()
} }
} }
@ -248,7 +357,7 @@ ApplicationWindow {
Action { Action {
text: qsTr("&Update LogarithmPlotter") text: qsTr("&Update LogarithmPlotter")
icon.name: 'update' icon.name: 'update'
onTriggered: Qt.openUrlExternally("https://apps.ad5001.eu/logarithmplotter/") onTriggered: Qt.openUrlExternally("https://dev.apps.ad5001.eu/logarithmplotter")
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,13 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick import QtQuick 2.12
import QtQuick.Controls import QtQuick.Controls 2.12
import Qt.labs.platform as Native
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import "../../js/historylib.mjs" as HistoryLib import "../../js/objects.js" as Objects
import "../../js/utils.mjs" as Utils import "../../js/historylib.js" as HistoryLib
import "../../js/mathlib.mjs" as MathLib import "../../js/utils.js" as Utils
import "../../js/mathlib.js" as MathLib
/*! /*!
\qmltype CustomPropertyList \qmltype CustomPropertyList
@ -43,11 +43,6 @@ Repeater {
Object whose properties to list and edit. Object whose properties to list and edit.
*/ */
property var obj property var obj
/*!
\qmlproperty var CustomPropertyList::positionPicker
Reference to the global PositionPicker QML object.
*/
property var positionPicker
readonly property var textTypes: ['Domain', 'string', 'number'] readonly property var textTypes: ['Domain', 'string', 'number']
readonly property var comboBoxTypes: ['ObjectType', 'Enum'] readonly property var comboBoxTypes: ['ObjectType', 'Enum']
@ -99,24 +94,17 @@ Repeater {
// Setting for text & number settings as well as domains // Setting for text & number settings as well as domains
Setting.TextSetting { Setting.TextSetting {
height: 30 height: 30
label: propertyLabel label: propertyLabel
icon: `settings/custom/${propertyIcon}.svg` icon: `settings/custom/${propertyIcon}.svg`
isDouble: propertyType == "number" isDouble: propertyType == 'number'
defValue: obj[propertyName] == null ? '' : obj[propertyName].toString() defValue: obj[propertyName] == null ? '' : obj[propertyName].toString()
category: {
return {
"Domain": "domain",
"string": "all",
"number": "all"
}[propertyType]
}
onChanged: function(newValue) { onChanged: function(newValue) {
try { try {
var newValueParsed = { var newValueParsed = {
"Domain": () => MathLib.parseDomain(newValue), 'Domain': () => MathLib.parseDomain(newValue),
"string": () => newValue, 'string': () => newValue,
"number": () => parseFloat(newValue) 'number': () => parseFloat(newValue)
}[propertyType]() }[propertyType]()
// Ensuring old and new values are different to prevent useless adding to history. // Ensuring old and new values are different to prevent useless adding to history.
@ -130,21 +118,20 @@ Repeater {
} }
} catch(e) { } catch(e) {
// Error in expression or domain // Error in expression or domain
console.trace()
parsingErrorDialog.showDialog(propertyName, newValue, e.message) parsingErrorDialog.showDialog(propertyName, newValue, e.message)
} }
} }
Native.MessageDialog { // D.MessageDialog {
id: parsingErrorDialog // id: parsingErrorDialog
title: qsTranslate("expression", "LogarithmPlotter - Parsing error") // title: qsTranslate("expression", "LogarithmPlotter - Parsing error")
text: "" // text: ""
function showDialog(propName, propValue, error) { // function showDialog(propName, propValue, error) {
text = qsTranslate("error", "Error while parsing expression for property %1:\n%2\n\nEvaluated expression: %3").arg(propName).arg(error).arg(propValue) // text = qsTranslate("error", "Error while parsing expression for property %1:\n%2\n\nEvaluated expression: %3").arg(propName).arg(error).arg(propValue)
open() // open()
} // }
} // }
} }
} }
@ -188,8 +175,8 @@ Repeater {
// Base, untranslated version of the model. // Base, untranslated version of the model.
property var baseModel: selectObjMode ? property var baseModel: selectObjMode ?
Modules.Objects.getObjectsName(propertyType.objType).concat( Objects.getObjectsName(propertyType.objType).concat(
isRealObject ? [qsTr("+ Create new %1").arg(Modules.Objects.types[propertyType.objType].displayType())] : []) isRealObject ? [qsTr("+ Create new %1").arg(Objects.types[propertyType.objType].displayType())] : [])
: propertyType.values : propertyType.values
// Translated version of the model. // Translated version of the model.
model: selectObjMode ? baseModel : propertyType.translatedValues model: selectObjMode ? baseModel : propertyType.translatedValues
@ -199,20 +186,20 @@ Repeater {
if(selectObjMode) { if(selectObjMode) {
// This is only done when what we're selecting are Objects. // This is only done when what we're selecting are Objects.
// Setting object property. // Setting object property.
var selectedObj = Modules.Objects.currentObjectsByName[baseModel[newIndex]] var selectedObj = Objects.currentObjectsByName[baseModel[newIndex]]
if(newIndex != 0) { if(newIndex != 0) {
// Make sure we don't set the object to null. // Make sure we don't set the object to null.
if(selectedObj == null) { if(selectedObj == null) {
// Creating new object. // Creating new object.
selectedObj = Modules.Objects.createNewRegisteredObject(propertyType.objType) selectedObj = Objects.createNewRegisteredObject(propertyType.objType)
history.addToHistory(new HistoryLib.CreateNewObject(selectedObj.name, propertyType.objType, selectedObj.export())) history.addToHistory(new HistoryLib.CreateNewObject(selectedObj.name, propertyType.objType, selectedObj.export()))
baseModel = Modules.Objects.getObjectsName(propertyType.objType).concat( baseModel = Objects.getObjectsName(propertyType.objType).concat(
isRealObject ? [qsTr("+ Create new %1").arg(Modules.Objects.types[propertyType.objType].displayType())] : isRealObject ? [qsTr("+ Create new %1").arg(Objects.types[propertyType.objType].displayType())] :
[]) [])
currentIndex = baseModel.indexOf(selectedObj.name) currentIndex = baseModel.indexOf(selectedObj.name)
} }
selectedObj.requiredBy.push(Modules.Objects.currentObjects[objType][objIndex]) selectedObj.requiredBy.push(Objects.currentObjects[objType][objIndex])
//Modules.Objects.currentObjects[objType][objIndex].requiredBy = obj[propertyName].filter((obj) => obj.name != obj.name) //Objects.currentObjects[objType][objIndex].requiredBy = obj[propertyName].filter((obj) => obj.name != obj.name)
} }
obj.requiredBy = obj.requiredBy.filter((obj) => obj.name != obj.name) obj.requiredBy = obj.requiredBy.filter((obj) => obj.name != obj.name)
history.addToHistory(new HistoryLib.EditedProperty( history.addToHistory(new HistoryLib.EditedProperty(
@ -256,7 +243,7 @@ Repeater {
obj.name, objType, propertyName, obj.name, objType, propertyName,
obj[propertyName], exported obj[propertyName], exported
)) ))
//Modules.Objects.currentObjects[objType][objIndex][propertyName] = exported //Objects.currentObjects[objType][objIndex][propertyName] = exported
obj[propertyName] = exported obj[propertyName] = exported
root.changed() root.changed()
} }
@ -268,68 +255,29 @@ Repeater {
} }
delegate: Component { delegate: Component {
Row { Loader {
//height: customPropComment.height + customPropText.height + customPropCheckBox.height + customPropCombo.height + customPropListDict.height
width: dlgProperties.width width: dlgProperties.width
spacing: 5 property string propertyName: modelData[0]
property var propertyType: modelData[1]
property string propertyLabel: qsTranslate('prop',propertyName)
property string propertyIcon: Utils.camelCase2readable(propertyName)
Loader { sourceComponent: {
id: propertyEditor if(propertyName.startsWith('comment'))
width: dlgProperties.width - pointerButton.width return commentComponent
property string propertyName: modelData[0] else if(propertyType == 'boolean')
property var propertyType: modelData[1] return checkboxComponent
property string propertyLabel: qsTranslate('prop',propertyName) else if(paramTypeIn(propertyType, ['Expression']))
property string propertyIcon: Utils.camelCase2readable(propertyName) return expressionEditorComponent
else if(paramTypeIn(propertyType, textTypes))
sourceComponent: { return textEditorComponent
if(propertyName.startsWith('comment')) else if(paramTypeIn(propertyType, comboBoxTypes))
return commentComponent return comboBoxComponent
else if(propertyType == 'boolean') else if(paramTypeIn(propertyType, listTypes))
return checkboxComponent return listDictEditorComponent
else if(paramTypeIn(propertyType, ['Expression'])) else
return expressionEditorComponent return {}
else if(paramTypeIn(propertyType, textTypes))
return textEditorComponent
else if(paramTypeIn(propertyType, comboBoxTypes))
return comboBoxComponent
else if(paramTypeIn(propertyType, listTypes))
return listDictEditorComponent
else
return {}
}
}
Button {
id: pointerButton
height: parent.height
width: visible ? height : 0
anchors.verticalCenter: parent.verticalCenter
property bool isXProp: ['labelX', 'x'].includes(propertyEditor.propertyName)
property bool isYProp: ['y'].includes(propertyEditor.propertyName)
visible: isXProp || isYProp
ToolTip.visible: hovered
ToolTip.text: qsTr("Pick on graph")
Setting.Icon {
id: icon
width: 18
height: 18
anchors.centerIn: parent
color: sysPalette.windowText
source: '../icons/common/position.svg'
}
onClicked: {
positionPicker.objType = objType
positionPicker.objName = obj.name
positionPicker.pickX = isXProp
positionPicker.pickY = isYProp
positionPicker.propertyX = propertyEditor.propertyName
positionPicker.propertyY = propertyEditor.propertyName
positionPicker.visible = true
objEditor.close()
}
} }
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,15 +16,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick import QtQuick 2.12
import QtQuick.Controls import QtQuick.Controls 2.12
import QtQuick.Dialogs as D import QtQuick.Dialogs 1.3 as D
import Qt.labs.platform as Native
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup import "../../js/objects.js" as Objects
import "../../js/historylib.mjs" as HistoryLib import "../../js/objs/common.js" as ObjectsCommons
import "../../js/utils.mjs" as Utils import "../../js/historylib.js" as HistoryLib
import "../../js/mathlib.mjs" as MathLib import "../../js/utils.js" as Utils
import "../../js/mathlib.js" as MathLib
/*! /*!
\qmltype Dialog \qmltype Dialog
@ -36,7 +36,7 @@ import "../../js/mathlib.mjs" as MathLib
\sa Loader, ObjectLists \sa Loader, ObjectLists
*/ */
Popup.BaseDialog { D.Dialog {
id: objEditor id: objEditor
/*! /*!
\qmlproperty string EditorDialog::objType \qmlproperty string EditorDialog::objType
@ -52,119 +52,103 @@ Popup.BaseDialog {
\qmlproperty var EditorDialog::obj \qmlproperty var EditorDialog::obj
Instance of the object being edited. Instance of the object being edited.
*/ */
property var obj: Modules.Objects.currentObjects[objType][objIndex] property var obj: Objects.currentObjects[objType][objIndex]
/*!
\qmlproperty var EditorDialog::posPicker
Reference to the global PositionPicker QML object.
*/
property var posPicker
title: "LogarithmPlotter" title: "LogarithmPlotter"
width: 350 width: 350
minimumHeight: Math.max(450,dlgProperties.height + margin*4 + 30) height: 400
maximumHeight: minimumHeight
Item { // Disable closing on return/enter, causing issues with autocomplete.
anchors { onActionChosen: if(action.key == Qt.Key_Enter || action.key == Qt.Key_Return) action.accepted = false
top: parent.top;
left: parent.left; Label {
bottom: parent.bottom; id: dlgTitle
right: parent.right; anchors.left: parent.left
topMargin: margin; anchors.top: parent.top
leftMargin: margin; verticalAlignment: TextInput.AlignVCenter
bottomMargin: margin + 30; text: qsTr("Edit properties of %1 %2").arg(Objects.types[objEditor.objType].displayType()).arg(objEditor.obj.name)
rightMargin: margin; font.pixelSize: 20
color: sysPalette.windowText
}
Column {
id: dlgProperties
anchors.top: dlgTitle.bottom
width: objEditor.width - 20
spacing: 10
D.MessageDialog {
id: invalidNameDialog
title: qsTr("LogarithmPlotter - Invalid object name")
text: ""
function showDialog(objectName) {
text = qsTr("An object with the name '%1' already exists.").arg(objectName)
open()
}
} }
Column { Setting.TextSetting {
id: dlgProperties id: nameProperty
anchors.top: parent.top height: 30
width: objEditor.width - 20 label: qsTr("Name")
spacing: 10 icon: "common/label.svg"
width: dlgProperties.width
Label { value: objEditor.obj.name
id: dlgTitle onChanged: function(newValue) {
verticalAlignment: TextInput.AlignVCenter let newName = Utils.parseName(newValue)
text: qsTr("Edit properties of %1 %2").arg(Modules.Objects.types[objEditor.objType].displayType()).arg(objEditor.obj.name) if(newName != '' && objEditor.obj.name != newName) {
font.pixelSize: 20 if(newName in Objects.currentObjectsByName) {
color: sysPalette.windowText invalidNameDialog.showDialog(newName)
} } else {
history.addToHistory(new HistoryLib.NameChanged(
Native.MessageDialog { objEditor.obj.name, objEditor.objType, newName
id: invalidNameDialog ))
title: qsTr("LogarithmPlotter - Invalid object name") Objects.renameObject(obj.name, newName)
text: "" objEditor.obj = Objects.currentObjects[objEditor.objType][objEditor.objIndex]
function showDialog(objectName) {
text = qsTr("An object with the name '%1' already exists.").arg(objectName)
open()
}
}
Setting.TextSetting {
id: nameProperty
height: 30
label: qsTr("Name")
icon: "common/label.svg"
category: "name"
width: dlgProperties.width
value: objEditor.obj.name
onChanged: function(newValue) {
let newName = Utils.parseName(newValue)
if(newName != '' && objEditor.obj.name != newName) {
if(newName in Modules.Objects.currentObjectsByName) {
invalidNameDialog.showDialog(newName)
} else {
history.addToHistory(new HistoryLib.NameChanged(
objEditor.obj.name, objEditor.objType, newName
))
Modules.Objects.renameObject(obj.name, newName)
objEditor.obj = Modules.Objects.currentObjects[objEditor.objType][objEditor.objIndex]
objectListList.update()
}
}
}
}
Setting.ComboBoxSetting {
id: labelContentProperty
height: 30
width: dlgProperties.width
label: qsTr("Label content")
model: [qsTr("null"), qsTr("name"), qsTr("name + value")]
property var idModel: ["null", "name", "name + value"]
icon: "common/label.svg"
currentIndex: idModel.indexOf(objEditor.obj.labelContent)
onActivated: function(newIndex) {
if(idModel[newIndex] != objEditor.obj.labelContent) {
objEditor.obj.labelContent = idModel[newIndex]
objEditor.obj.update()
objectListList.update() objectListList.update()
} }
} }
} }
}
// Dynamic properties
CustomPropertyList { Setting.ComboBoxSetting {
id: dlgCustomProperties id: labelContentProperty
obj: objEditor.obj height: 30
positionPicker: posPicker width: dlgProperties.width
label: qsTr("Label content")
onChanged: { model: [qsTr("null"), qsTr("name"), qsTr("name + value")]
obj.update() property var idModel: ["null", "name", "name + value"]
icon: "common/label.svg"
currentIndex: idModel.indexOf(objEditor.obj.labelContent)
onActivated: function(newIndex) {
if(idModel[newIndex] != objEditor.obj.labelContent) {
objEditor.obj.labelContent = idModel[newIndex]
objEditor.obj.update()
objectListList.update() objectListList.update()
} }
} }
} }
// Dynamic properties
CustomPropertyList {
id: dlgCustomProperties
obj: objEditor.obj
onChanged: {
obj.update()
objectListList.update()
}
}
} }
/*! /*!
\qmlmethod void EditorDialog::open() \qmlmethod void EditorDialog::show()
Shows the editor after the object to be edited is set. Shows the editor after the object to be edited is set.
*/ */
function open() { function show() {
dlgCustomProperties.model = [] // Reset dlgCustomProperties.model = [] // Reset
let objProps = Modules.Objects.types[objEditor.objType].properties() let objProps = Objects.types[objEditor.objType].properties()
dlgCustomProperties.model = Object.keys(objProps).map(prop => [prop, objProps[prop]]) // Converted to 2-dimentional array. dlgCustomProperties.model = Object.keys(objProps).map(prop => [prop, objProps[prop]]) // Converted to 2-dimentional array.
objEditor.show() objEditor.open()
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,9 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick import QtQuick 2.12
import QtQuick.Controls import QtQuick.Controls 2.12
import "../js/historylib.mjs" as HistoryLib import "../js/objects.js" as Objects
import "../js/historylib.js" as HistoryLib
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
@ -33,21 +34,6 @@ Column {
id: createRow id: createRow
property var objectEditor property var objectEditor
property var objectLists property var objectLists
property var posPicker
/*!
\qmlmethod int ObjectCreationGrid::openEditorDialog(var obj)
Opens the editor dialog for an object \c obj.
*/
function openEditorDialog(obj) {
// Open editor
objectEditor.obj = obj
objectEditor.objType = obj.type
objectEditor.objIndex = Modules.Objects.currentObjects[obj.type].indexOf(obj)
objectEditor.open()
// Disconnect potential link
posPicker.picked.disconnect(openEditorDialog)
}
Label { Label {
id: createTitle id: createTitle
@ -60,12 +46,12 @@ Column {
width: parent.width width: parent.width
columns: 3 columns: 3
Repeater { Repeater {
model: Object.keys(Modules.Objects.types) model: Object.keys(Objects.types)
Button { Button {
id: createBtn id: createBtn
width: 96 width: 96
visible: Modules.Objects.types[modelData].createable() visible: Objects.types[modelData].createable()
height: visible ? width*0.8 : 0 height: visible ? width*0.8 : 0
// The KDE SDK is kinda buggy, so it respects neither specified color nor display propreties. // The KDE SDK is kinda buggy, so it respects neither specified color nor display propreties.
//display: AbstractButton.TextUnderIcon //display: AbstractButton.TextUnderIcon
@ -93,7 +79,7 @@ Column {
anchors.rightMargin: 4 anchors.rightMargin: 4
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
font.pixelSize: 14 font.pixelSize: 14
text: Modules.Objects.types[modelData].displayType() text: Objects.types[modelData].displayType()
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
clip: true clip: true
} }
@ -103,26 +89,13 @@ Column {
ToolTip.text: label.text ToolTip.text: label.text
onClicked: { onClicked: {
let newObj = Modules.Objects.createNewRegisteredObject(modelData) var newObj = Objects.createNewRegisteredObject(modelData)
history.addToHistory(new HistoryLib.CreateNewObject(newObj.name, modelData, newObj.export())) history.addToHistory(new HistoryLib.CreateNewObject(newObj.name, modelData, newObj.export()))
objectLists.update() objectLists.update()
objectEditor.obj = Objects.currentObjects[modelData][Objects.currentObjects[modelData].length - 1]
let hasXProp = newObj.constructor.properties().hasOwnProperty('x') objectEditor.objType = modelData
let hasYProp = newObj.constructor.properties().hasOwnProperty('y') objectEditor.objIndex = Objects.currentObjects[modelData].length - 1
if(hasXProp || hasYProp) { objectEditor.show()
// Open picker
posPicker.objType = newObj.type
posPicker.objName = newObj.name
posPicker.pickX = hasXProp
posPicker.pickY = hasYProp
posPicker.propertyX = 'x'
posPicker.propertyY = 'y'
posPicker.visible = true
posPicker.picked.connect(openEditorDialog)
} else {
// Open editor
openEditorDialog(newObj)
}
} }
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,11 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick import QtQuick 2.12
// import QtQuick.Dialogs 1.3 as D import QtQuick.Dialogs 1.3 as D
import QtQuick.Controls import QtQuick.Controls 2.12
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import eu.ad5001.LogarithmPlotter.ObjectLists.Editor 1.0 as Editor import eu.ad5001.LogarithmPlotter.ObjectLists.Editor 1.0 as Editor
import "../js/objects.js" as Objects
/*! /*!
\qmltype ObjectLists \qmltype ObjectLists
@ -46,7 +47,7 @@ ScrollView {
ListView { ListView {
id: objectsListView id: objectsListView
model: Object.keys(Modules.Objects.types) model: Object.keys(Objects.types)
//width: implicitWidth //objectListList.width - (implicitHeight > objectListList.parent.height ? 20 : 0) //width: implicitWidth //objectListList.width - (implicitHeight > objectListList.parent.height ? 20 : 0)
implicitHeight: contentItem.childrenRect.height + footerItem.height + 10 implicitHeight: contentItem.childrenRect.height + footerItem.height + 10
@ -54,7 +55,7 @@ ScrollView {
id: objTypeList id: objTypeList
property string objType: objectsListView.model[index] property string objType: objectsListView.model[index]
property var editingRows: [] property var editingRows: []
model: Modules.Objects.currentObjects[objType] model: Objects.currentObjects[objType]
width: objectsListView.width width: objectsListView.width
implicitHeight: contentItem.childrenRect.height implicitHeight: contentItem.childrenRect.height
visible: model != undefined && model.length > 0 visible: model != undefined && model.length > 0
@ -69,23 +70,21 @@ ScrollView {
CheckBox { CheckBox {
id: typeVisibilityCheckBox id: typeVisibilityCheckBox
checked: Modules.Objects.currentObjects[objType] != undefined ? Modules.Objects.currentObjects[objType].every(obj => obj.visible) : true checked: Objects.currentObjects[objType] != undefined ? Objects.currentObjects[objType].every(obj => obj.visible) : true
onClicked: { onClicked: {
for(var obj of Modules.Objects.currentObjects[objType]) obj.visible = this.checked for(var obj of Objects.currentObjects[objType]) obj.visible = this.checked
for(var obj of objTypeList.editingRows) obj.objVisible = this.checked for(var obj of objTypeList.editingRows) obj.objVisible = this.checked
objectListList.changed() objectListList.changed()
} }
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: checked ? ToolTip.text: checked ? qsTr("Hide all %1").arg(Objects.types[objType].displayTypeMultiple()) : qsTr("Show all %1").arg(Objects.types[objType].displayTypeMultiple())
qsTr("Hide all %1").arg(Modules.Objects.types[objType].displayTypeMultiple()) :
qsTr("Show all %1").arg(Modules.Objects.types[objType].displayTypeMultiple())
} }
Label { Label {
id: typeHeaderText id: typeHeaderText
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
text: qsTranslate("control", "%1: ").arg(Modules.Objects.types[objType].displayTypeMultiple()) text: qsTranslate("control", "%1: ").arg(Objects.types[objType].displayTypeMultiple())
font.pixelSize: 20 font.pixelSize: 20
} }
} }
@ -93,11 +92,11 @@ ScrollView {
delegate: ObjectRow { delegate: ObjectRow {
id: controlRow id: controlRow
width: objTypeList.width width: objTypeList.width
obj: Modules.Objects.currentObjects[objType][index] obj: Objects.currentObjects[objType][index]
posPicker: positionPicker posPicker: positionPicker
onChanged: { onChanged: {
obj = Modules.Objects.currentObjects[objType][index] obj = Objects.currentObjects[objType][index]
objectListList.update() objectListList.update()
} }
@ -111,15 +110,12 @@ ScrollView {
width: objectsListView.width width: objectsListView.width
objectEditor: objEditor objectEditor: objEditor
objectLists: objectListList objectLists: objectListList
posPicker: positionPicker
} }
} }
// Object editor // Object editor
Editor.Dialog { Editor.Dialog {
id: objEditor id: objEditor
posPicker: positionPicker
} }
/*! /*!
@ -129,7 +125,7 @@ ScrollView {
function update() { function update() {
objectListList.changed() objectListList.changed()
for(var objType in objectListList.listViews) { for(var objType in objectListList.listViews) {
objectListList.listViews[objType].model = Modules.Objects.currentObjects[objType] objectListList.listViews[objType].model = Objects.currentObjects[objType]
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,12 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick import QtQuick 2.12
import QtQuick.Dialogs import QtQuick.Dialogs 1.3 as D
import QtQuick.Controls import QtQuick.Controls 2.12
import QtQuick.Window
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import "../js/historylib.mjs" as HistoryLib import "../js/objects.js" as Objects
import "../js/historylib.js" as HistoryLib
import "../js/math/latex.js" as LatexJS
/*! /*!
@ -89,18 +90,18 @@ Item {
id: objDescription id: objDescription
anchors.left: objVisibilityCheckBox.right anchors.left: objVisibilityCheckBox.right
anchors.right: deleteButton.left anchors.right: deleteButton.left
height: Modules.Latex.enabled ? Math.max(parent.minHeight, latexDescription.height+4) : parent.minHeight height: LatexJS.enabled ? Math.max(parent.minHeight, latexDescription.height+4) : parent.minHeight
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
text: Modules.Latex.enabled ? "" : obj.getReadableString() text: LatexJS.enabled ? "" : obj.getReadableString()
font.pixelSize: 14 font.pixelSize: 14
Image { Image {
id: latexDescription id: latexDescription
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left anchors.left: parent.left
visible: Modules.Latex.enabled visible: LatexJS.enabled
property double depth: Screen.devicePixelRatio property double depth: 2
property var ltxInfo: visible ? Latex.render(obj.getLatexString(), depth*(parent.font.pixelSize+2), parent.color).split(",") : ["","0","0"] property var ltxInfo: visible ? Latex.render(obj.getLatexString(), depth*parent.font.pixelSize+4, parent.color).split(",") : ["","0","0"]
source: visible ? ltxInfo[0] : "" source: visible ? ltxInfo[0] : ""
width: parseInt(ltxInfo[1])/depth width: parseInt(ltxInfo[1])/depth
height: parseInt(ltxInfo[2])/depth height: parseInt(ltxInfo[2])/depth
@ -109,11 +110,11 @@ Item {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
objEditor.obj = Modules.Objects.currentObjects[obj.type][index] objEditor.obj = Objects.currentObjects[obj.type][index]
objEditor.objType = obj.type objEditor.objType = obj.type
objEditor.objIndex = index objEditor.objIndex = index
//objEditor.editingRow = objectRow //objEditor.editingRow = objectRow
objEditor.open() objEditor.show()
} }
} }
} }
@ -191,15 +192,15 @@ Item {
} }
} }
ColorDialog { D.ColorDialog {
id: pickColor id: pickColor
selectedColor: obj.color color: obj.color
title: qsTr("Pick new color for %1 %2").arg(obj.constructor.displayType()).arg(obj.name) title: qsTr("Pick new color for %1 %2").arg(obj.constructor.displayType()).arg(obj.name)
onAccepted: { onAccepted: {
history.addToHistory(new HistoryLib.ColorChanged( history.addToHistory(new HistoryLib.ColorChanged(
obj.name, obj.type, obj.color, selectedColor.toString() obj.name, obj.type, obj.color, color.toString()
)) ))
obj.color = selectedColor.toString() obj.color = color.toString()
changed() changed()
} }
} }
@ -211,14 +212,10 @@ Item {
function deleteRecursively(object) { function deleteRecursively(object) {
for(let toRemove of object.requiredBy) for(let toRemove of object.requiredBy)
deleteRecursively(toRemove) deleteRecursively(toRemove)
if(Modules.Objects.currentObjectsByName[object.name] != undefined) { object.requiredBy = []
// Object still exists history.addToHistory(new HistoryLib.DeleteObject(
// Temporary fix for objects require not being propertly updated. object.name, object.type, object.export()
object.requiredBy = [] ))
history.addToHistory(new HistoryLib.DeleteObject( Objects.deleteObject(object.name)
object.name, object.type, object.export()
))
Modules.Objects.deleteObject(object.name)
}
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,11 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick import QtQuick 2.12
import QtQuick.Controls import QtQuick.Controls 2.12
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting import "js/objects.js" as Objects
import "js/mathlib.mjs" as MathLib import "js/mathlib.js" as MathLib
import "js/historylib.mjs" as HistoryLib import "js/historylib.js" as HistoryLib
/*! /*!
\qmltype PickLocationOverlay \qmltype PickLocationOverlay
@ -36,15 +36,6 @@ import "js/historylib.mjs" as HistoryLib
Item { Item {
id: pickerRoot id: pickerRoot
visible: false visible: false
clip: true
/*!
\qmlsignal PickLocationOverlay::picked(var obj)
Emitted when a location has been picked
The corresponding handler is \c onPicked.
*/
signal picked(var obj)
/*! /*!
\qmlproperty var PickLocationOverlay::canvas \qmlproperty var PickLocationOverlay::canvas
@ -63,12 +54,12 @@ Item {
property string objName: 'A' property string objName: 'A'
/*! /*!
\qmlproperty bool PickLocationOverlay::pickX \qmlproperty bool PickLocationOverlay::pickX
true if the property in propertyX is pickable. true if the user should be picking a position on the x axis.
*/ */
property bool pickX: true property bool pickX: true
/*! /*!
\qmlproperty bool PickLocationOverlay::pickY \qmlproperty bool PickLocationOverlay::pickY
true if the property in propertyY is pickable. true if the user should be picking a position on the y axis.
*/ */
property bool pickY: true property bool pickY: true
/*! /*!
@ -86,16 +77,6 @@ Item {
Precision of the picked value (post-dot precision). Precision of the picked value (post-dot precision).
*/ */
property alias precision: precisionSlider.value property alias precision: precisionSlider.value
/*!
\qmlproperty bool PickLocationOverlay::userPickX
true if the user can and wants to be picking a position on the x axis.
*/
readonly property bool userPickX: pickX && pickXCheckbox.checked
/*!
\qmlproperty bool PickLocationOverlay::userPickY
true if the user can and wants to be picking a position on the y axis.
*/
readonly property bool userPickY: pickY && pickYCheckbox.checked
Rectangle { Rectangle {
color: sysPalette.window color: sysPalette.window
@ -109,150 +90,62 @@ Item {
hoverEnabled: parent.visible hoverEnabled: parent.visible
cursorShape: Qt.CrossCursor cursorShape: Qt.CrossCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: function(mouse) { onClicked: {
if(mouse.button == Qt.LeftButton) { // Validate if(mouse.button == Qt.LeftButton) { // Validate
let newValueX = !parent.userPickX ? null : parseValue(picked.mouseX.toString(), objType, propertyX) let newValueX = !parent.pickX ? null : parseValue(picked.mouseX.toString(), objType, propertyX)
let newValueY = !parent.userPickY ? null : parseValue(picked.mouseY.toString(), objType, propertyY) let newValueY = !parent.pickY ? null : parseValue(picked.mouseY.toString(), objType, propertyY)
let obj = Modules.Objects.currentObjectsByName[objName] let obj = Objects.currentObjectsByName[objName]
// Set values // Set values
if(parent.userPickX && parent.userPickY) { if(parent.pickX && parent.pickY) {
history.addToHistory(new HistoryLib.EditedPosition( history.addToHistory(new HistoryLib.EditedPosition(
objName, objType, obj[propertyX], newValueX, obj[propertyY], newValueY objName, objType, obj[propertyX], newValueX, obj[propertyY], newValueY
)) ))
obj[propertyX] = newValueX obj[propertyX] = newValueX
obj[propertyY] = newValueY obj[propertyY] = newValueY
obj.update() } else if(parent.pickX) {
objectLists.update()
pickerRoot.picked(obj)
} else if(parent.userPickX) {
history.addToHistory(new HistoryLib.EditedProperty( history.addToHistory(new HistoryLib.EditedProperty(
objName, objType, propertyX, obj[propertyX], newValueX objName, objType, propertyX, obj[propertyX], newValueX
)) ))
obj[propertyX] = newValueX obj[propertyX] = newValueX
obj.update() } else if(parent.pickY) {
objectLists.update()
pickerRoot.picked(obj)
} else if(parent.userPickY) {
history.addToHistory(new HistoryLib.EditedProperty( history.addToHistory(new HistoryLib.EditedProperty(
objName, objType, propertyY, obj[propertyY], newValueY objName, objType, propertyY, obj[propertyY], newValueY
)) ))
obj[propertyY] = newValueY obj[propertyY] = newValueY
obj.update()
objectLists.update()
pickerRoot.picked(obj)
} }
obj.update()
objectLists.update()
} }
pickerRoot.visible = false; pickerRoot.visible = false;
} }
} }
Row {
height: precisionSlider.height
Rectangle { Text {
id: pickerSettings text: " "+ qsTr("Pointer precision:") + " "
radius: 15 color: 'black'
color: sysPalette.window anchors.verticalCenter: parent.verticalCenter
width: pickerSettingsColumn.width + 30; }
height: pickerSettingsColumn.height + 20
property bool folded: false;
x: -15 - ((width-55) * folded);
y: 10
z: 2
Row { Slider {
id: pickerSettingsColumn id: precisionSlider
anchors { from: 0
left: parent.left value: 2
top: parent.top to: 10
leftMargin: 20 stepSize: 1
topMargin: 10 ToolTip {
} parent: precisionSlider.handle
spacing: 15 visible: precisionSlider.pressed
property int cellHeight: 15 text: precisionSlider.value.toFixed(0)
Column {
spacing: 5
// width: 100
Text {
text: qsTr("Pointer precision:")
color: sysPalette.windowText
verticalAlignment: Text.AlignVCenter
height: pickerSettingsColumn.cellHeight
}
Text {
text: qsTr("Snap to grid:")
color: sysPalette.windowText
verticalAlignment: Text.AlignVCenter
height: pickerSettingsColumn.cellHeight
}
CheckBox {
id: pickXCheckbox
height: pickerSettingsColumn.cellHeight
text: qsTr("Pick X")
checked: pickX
visible: pickX
}
}
Column {
spacing: 5
Slider {
id: precisionSlider
from: 0
value: 2
to: 10
stepSize: 1
height: pickerSettingsColumn.cellHeight
ToolTip {
parent: precisionSlider.handle
visible: precisionSlider.pressed
text: precisionSlider.value.toFixed(0)
}
}
CheckBox {
id: snapToGridCheckbox
height: pickerSettingsColumn.cellHeight
// text: qsTr("Snap to grid")
checked: false
}
CheckBox {
id: pickYCheckbox
height: pickerSettingsColumn.cellHeight
text: qsTr("Pick Y")
checked: pickY
visible: pickY
}
}
Button {
width: 24
anchors.top: parent.top
anchors.bottom: parent.bottom
flat: true
onClicked: pickerSettings.folded = !pickerSettings.folded
ToolTip.visible: hovered
ToolTip.delay: 200
ToolTip.text: pickerSettings.folded ? qsTr("Open picker settings") : qsTr("Hide picker settings")
Setting.Icon {
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
width: 18
height: 18
color: sysPalette.windowText
source: `../icons/common/settings.svg`
}
} }
} }
CheckBox {
id: snapToGridCheckbox
text: qsTr("Snap to grid")
checked: false
}
} }
Rectangle { Rectangle {
@ -262,8 +155,8 @@ Item {
color: 'black' color: 'black'
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Modules.Canvas.x2px(picked.mouseX) anchors.leftMargin: canvas.x2px(picked.mouseX)
visible: parent.userPickX visible: parent.pickX
} }
Rectangle { Rectangle {
@ -273,48 +166,45 @@ Item {
color: 'black' color: 'black'
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.topMargin: Modules.Canvas.y2px(picked.mouseY) anchors.topMargin: canvas.y2px(picked.mouseY)
visible: parent.userPickY visible: parent.pickY
} }
Text { Text {
id: picked id: picked
x: picker.mouseX - width - 5 x: picker.mouseX - width - 5
y: picker.mouseY - height - 5 y: picker.mouseY - height - 5
color: 'black' property double axisX: canvas.xaxisstep1
property double axisX: Modules.Canvas.axesStep.x.value
property double axisY: Modules.Canvas.axesStep.y.value
property double mouseX: { property double mouseX: {
let xpos = Modules.Canvas.px2x(picker.mouseX) let xpos = canvas.px2x(picker.mouseX)
if(snapToGridCheckbox.checked) { if(snapToGridCheckbox.checked) {
if(canvas.logscalex) { if(canvas.logscalex) {
// Calculate the logged power // Calculate the logged power
let pow = Math.pow(10, Math.floor(Math.log10(xpos))) let pow = Math.pow(10, Math.floor(Math.log10(xpos)))
return pow*Math.round(xpos/pow) return pow*Math.round(xpos/pow)
} else { } else {
return axisX*Math.round(xpos/axisX) return canvas.xaxisstep1*Math.round(xpos/canvas.xaxisstep1)
} }
} else { } else {
return xpos.toFixed(parent.precision) return xpos.toFixed(parent.precision)
} }
} }
property double mouseY: { property double mouseY: {
let ypos = Modules.Canvas.px2y(picker.mouseY) let ypos = canvas.px2y(picker.mouseY)
if(snapToGridCheckbox.checked) { if(snapToGridCheckbox.checked) {
return axisY*Math.round(ypos/axisY) return canvas.yaxisstep1*Math.round(ypos/canvas.yaxisstep1)
} else { } else {
return ypos.toFixed(parent.precision) return ypos.toFixed(parent.precision)
} }
} }
color: 'black'
text: { text: {
if(parent.userPickX && parent.userPickY) if(parent.pickX && parent.pickY)
return `(${mouseX}, ${mouseY})` return `(${mouseX}, ${mouseY})`
else if(parent.userPickX) if(parent.pickX)
return `X = ${mouseX}` return `X = ${mouseX}`
else if(parent.userPickY) if(parent.pickY)
return `Y = ${mouseY}` return `Y = ${mouseY}`
else
return qsTr('(no pick selected)')
} }
} }
@ -324,7 +214,7 @@ Item {
Parses a given \c value as an expression or a number depending on the type of \c propertyName of all \c objType. Parses a given \c value as an expression or a number depending on the type of \c propertyName of all \c objType.
*/ */
function parseValue(value, objType, propertyName) { function parseValue(value, objType, propertyName) {
if(Modules.Objects.types[objType].properties()[propertyName] == 'number') if(Objects.types[objType].properties()[propertyName] == 'number')
return parseFloat(value) return parseFloat(value)
else else
return new MathLib.Expression(value) return new MathLib.Expression(value)

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,8 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick import QtQuick 2.12
import QtQuick.Controls import QtQuick.Dialogs 1.3 as D
import QtQuick.Controls 2.12
/*! /*!
\qmltype About \qmltype About
@ -26,112 +27,98 @@ import QtQuick.Controls
\sa LogarithmPlotter \sa LogarithmPlotter
*/ */
BaseDialog { D.Dialog {
id: about id: about
title: qsTr("About LogarithmPlotter") title: qsTr("About LogarithmPlotter")
width: 400 width: 400
minimumHeight: 600 height: 600
Item { Image {
anchors { id: logo
top: parent.top; source: "../icons/logarithmplotter.svg"
left: parent.left; sourceSize.width: 64
bottom: parent.bottom; sourceSize.height: 64
right: parent.right; width: 64
topMargin: margin; height: 64
leftMargin: margin; anchors.horizontalCenter: parent.horizontalCenter
bottomMargin: margin; anchors.rightMargin: width/2
rightMargin: margin; anchors.top: parent.top
} anchors.topMargin: 10
}
Image {
id: logo Label {
source: "../icons/logarithmplotter.svg" id: appName
sourceSize.width: 64 anchors.top: logo.bottom
sourceSize.height: 64 anchors.left: parent.left
width: 64 anchors.topMargin: 10
height: 64 horizontalAlignment: Text.AlignHCenter
anchors.horizontalCenter: parent.horizontalCenter width: parent.width
anchors.rightMargin: width/2 wrapMode: Text.WordWrap
anchors.top: parent.top font.pixelSize: 25
anchors.topMargin: 10 text: qsTr("LogarithmPlotter v%1").arg(Helper.getVersion())
} }
Label { Label {
id: appName id: description
anchors.top: logo.bottom anchors.top: appName.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.topMargin: 10 anchors.topMargin: 10
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
width: parent.width width: parent.width
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
font.pixelSize: 25 font.pixelSize: 18
text: qsTr("LogarithmPlotter v%1").arg(Helper.getVersion()) text: qsTr("2D plotter software to make BODE plots, sequences and repartition functions.")
} }
Label { Label {
id: description id: debugInfos
anchors.top: appName.bottom anchors.top: description.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.topMargin: 10 anchors.topMargin: 10
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
width: parent.width width: parent.width
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
font.pixelSize: 18 font.pixelSize: 14
text: qsTr("2D plotter software to make BODE plots, sequences and repartition functions.") text: Helper.getDebugInfos()
} }
Label { Label {
id: debugInfos id: copyrightInfos
anchors.top: description.bottom anchors.top: debugInfos.bottom
anchors.left: parent.left anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 10 anchors.topMargin: 10
horizontalAlignment: Text.AlignHCenter width: Math.min(410, parent.width)
width: parent.width wrapMode: Text.WordWrap
wrapMode: Text.WordWrap textFormat: Text.RichText
font.pixelSize: 14 font.pixelSize: 13
text: Helper.getDebugInfos() text: "Copyright © 2022 Ad5001 &lt;mail@ad5001.eu&gt;<br>
}
Label {
id: copyrightInfos
anchors.top: debugInfos.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 10
width: Math.min(410, parent.width)
wrapMode: Text.WordWrap
textFormat: Text.RichText
font.pixelSize: 13
text: "Copyright © 2021-2024 Ad5001 &lt;mail@ad5001.eu&gt;<br>
<br> <br>
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.<br> 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.<br>
<br> <br>
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.<br> 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.<br>
<br> <br>
You should have received a copy of the GNU General Public License along with this program. If not, see <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>." You should have received a copy of the GNU General Public License along with this program. If not, see <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>."
onLinkActivated: Qt.openUrlExternally(link) onLinkActivated: Qt.openUrlExternally(link)
}
Row {
anchors.top: copyrightInfos.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 10
spacing: 5
Button {
id: openIssueButton
text: qsTr('Report a bug')
icon.name: 'tools-report-bug'
onClicked: Qt.openUrlExternally('https://git.ad5001.eu/Ad5001/LogarithmPlotter')
} }
Row { Button {
id: buttonsRow id: officialWebsiteButton
anchors.top: copyrightInfos.bottom text: qsTr('Official website')
anchors.horizontalCenter: parent.horizontalCenter icon.name: 'web-browser'
anchors.topMargin: 10 onClicked: Qt.openUrlExternally('https://apps.ad5001.eu/logarithmplotter/')
spacing: 5
Button {
id: openIssueButton
text: qsTr('Report a bug')
icon.name: 'tools-report-bug'
onClicked: Qt.openUrlExternally('https://git.ad5001.eu/Ad5001/LogarithmPlotter')
}
Button {
id: officialWebsiteButton
text: qsTr('Official website')
icon.name: 'web-browser'
onClicked: Qt.openUrlExternally('https://apps.ad5001.eu/logarithmplotter/')
}
} }
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick import QtQuick 2.12
/*! /*!
\qmltype Alert \qmltype Alert
\inqmlmodule eu.ad5001.LogarithmPlotter.Popup \inqmlmodule eu.ad5001.LogarithmPlotter.Popup

View file

@ -1,59 +0,0 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick
import QtQuick.Controls
/*!
\qmltype BaseDialog
\inqmlmodule eu.ad5001.LogarithmPlotter.Popup
\brief Base dialog window in replacement of Dialog Popup from Qt 5.
\sa LogarithmPlotter
*/
Window {
id: base
color: sysPalette.window
visible: false;
flags: Qt.Dialog | Qt.Popup | Qt.MSWindowsFixedSizeDialogHint
modality: Qt.WindowModal
minimumWidth: width
maximumWidth: width
height: minimumHeight
property int margin: 10
Button {
id: closeButton
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.bottomMargin: margin
anchors.rightMargin: margin
text: qsTr('Close')
onClicked: close()
}
Shortcut {
sequence: "Esc"
onActivated: base.close()
}
function open() {
show()
}
}

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,8 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick import QtQuick 2.12
import QtQuick.Controls import QtQuick.Controls 2.12
/*! /*!
\qmltype Changelog \qmltype Changelog
@ -32,7 +32,7 @@ Popup {
id: changelogPopup id: changelogPopup
x: (parent.width-width)/2 x: (parent.width-width)/2
y: Math.max(20, (parent.height-height)/2) y: Math.max(20, (parent.height-height)/2)
width: 800 width: changelog.width+40
height: Math.min(parent.height-40, 500) height: Math.min(parent.height-40, 500)
modal: true modal: true
focus: true focus: true
@ -44,62 +44,42 @@ Popup {
*/ */
property bool changelogNeedsFetching: true property bool changelogNeedsFetching: true
onAboutToShow: if(changelogNeedsFetching) { onAboutToShow: if(changelogNeedsFetching) Helper.fetchChangelog()
Helper.fetchChangelog()
}
Connections { Connections {
target: Helper target: Helper
function onChangelogFetched(chl) { function onChangelogFetched(chl) {
changelogNeedsFetching = false; changelogNeedsFetching = false;
changelog.text = chl changelog.text = chl
changelogView.contentItem.implicitHeight = changelog.height
// console.log(changelog.height, changelogView.contentItem.implicitHeight)
} }
} }
ScrollView { ScrollView {
id: changelogView
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 10 anchors.topMargin: 10
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 10 anchors.leftMargin: 10
anchors.right: parent.right
anchors.rightMargin: 10
anchors.bottom: doneBtn.top anchors.bottom: doneBtn.top
anchors.bottomMargin: 10 anchors.bottomMargin: 10
clip: true clip: true
Label { Label {
id: changelog id: changelog
color: sysPalette.windowText color: sysPalette.windowText
width: 760
wrapMode: Text.WordWrap
textFormat: TextEdit.MarkdownText textFormat: TextEdit.MarkdownText
text: qsTr("Fetching changelog...") text: qsTr("Fetching changelog...")
onLinkActivated: Qt.openUrlExternally(link) onLinkActivated: Qt.openUrlExternally(link)
} }
} }
Rectangle {
id: bottomSeparator
opacity: 0.3
color: sysPalette.windowText
width: parent.width * 2 / 3
height: 1
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: doneBtn.top
anchors.bottomMargin: 7
}
Button { Button {
id: doneBtn id: doneBtn
text: qsTr("Close") text: qsTr("Done")
font.pixelSize: 18 font.pixelSize: 18
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.bottomMargin: 7 anchors.bottomMargin: 10
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
onClicked: changelogPopup.close() onClicked: changelogPopup.close()
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import Qt.labs.platform import QtQuick.Dialogs 1.3 as D
/*! /*!
\qmltype FileDialog \qmltype FileDialog
@ -25,7 +25,7 @@ import Qt.labs.platform
\sa LogarithmPlotter, Settings \sa LogarithmPlotter, Settings
*/ */
FileDialog { D.FileDialog {
id: fileDialog id: fileDialog
property bool exportMode: false property bool exportMode: false
@ -33,6 +33,6 @@ FileDialog {
title: exportMode ? qsTr("Export Logarithm Plot file") : qsTr("Import Logarithm Plot file") title: exportMode ? qsTr("Export Logarithm Plot file") : qsTr("Import Logarithm Plot file")
nameFilters: ["Logarithm Plot File (*.lpf)", "All files (*)"] nameFilters: ["Logarithm Plot File (*.lpf)", "All files (*)"]
defaultSuffix: 'lpf' folder: shortcuts.documents
fileMode: exportMode ? FileDialog.SaveFile : FileDialog.OpenFile selectExisting: !exportMode
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,9 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick import QtQuick 2.12
import QtQuick.Controls import QtQuick.Controls 2.12
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting import "../js/math/latex.js" as Latex
/*! /*!
\qmltype GreetScreen \qmltype GreetScreen
@ -33,122 +33,175 @@ Popup {
id: greetingPopup id: greetingPopup
x: (parent.width-width)/2 x: (parent.width-width)/2
y: Math.max(20, (parent.height-height)/2) y: Math.max(20, (parent.height-height)/2)
width: greetingLayout.width+20 width: Math.max(welcome.width+70, checkForUpdatesSetting.width, resetRedoStackSetting.width)+20
height: Math.min(parent.height-40, 700) height: Math.min(parent.height-40, 500)
modal: true modal: true
focus: true focus: true
clip: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
Column { Item {
id: greetingLayout id: welcome
width: 600 height: logo.height
spacing: 10 width: logo.width + 10 + welcomeText.width
clip: true anchors.top: parent.top
topPadding: 35 anchors.topMargin: (parent.width-width)/2
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())
}
}
Grid {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: greetingLayout.bottom
anchors.topMargin: 50
columns: 2
spacing: 10
Repeater { Image {
model: [{ id: logo
name: qsTr("Changelog"), source: "../icons/logarithmplotter.svg"
icon: 'common/new.svg', sourceSize.width: 48
onClicked: () => changelog.open() sourceSize.height: 48
},{ width: 48
name: qsTr("Preferences"), height: 48
icon: 'common/settings.svg', }
onClicked: () => preferences.open()
},{ Label {
name: qsTr("User manual"), id: welcomeText
icon: 'common/manual.svg', anchors.verticalCenter: parent.verticalCenter
onClicked: () => Qt.openUrlExternally("https://git.ad5001.eu/Ad5001/LogarithmPlotter/wiki/_Sidebar") anchors.left: logo.right
},{ anchors.leftMargin: 10
name: qsTr("Close"), //width: parent.width
icon: 'common/close.svg', wrapMode: Text.WordWrap
onClicked: () => greetingPopup.close() font.pixelSize: 32
}] text: qsTr("Welcome to LogarithmPlotter")
Button {
id: createBtn
width: 96
height: 96
onClicked: modelData.onClicked()
Setting.Icon {
id: icon
width: 24
height: 24
anchors {
left: parent.left
leftMargin: (parent.width-width)/2
top: parent.top
topMargin: (label.y-height)/2
}
color: sysPalette.windowText
source: '../icons/' + modelData.icon
}
Label {
id: label
anchors {
bottom: parent.bottom
bottomMargin: 5
left: parent.left
leftMargin: 4
right: parent.right
rightMargin: 4
}
horizontalAlignment: Text.AlignHCenter
font.pixelSize: 14
text: modelData.name
wrapMode: Text.WordWrap
clip: true
}
}
} }
} }
Component.onCompleted: if(Helper.getSetting("last_install_greet") != Helper.getVersion()+1) { Label {
id: versionText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: welcome.bottom
anchors.topMargin: 10
//width: parent.width
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
anchors.top: versionText.bottom
anchors.topMargin: 40
//width: parent.width
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
//anchors.horizontalCenter: parent.horizontalCenter
anchors.top: helpText.bottom
anchors.topMargin: 10
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.settings.children[0].checked = checked
}
}
CheckBox {
id: resetRedoStackSetting
anchors.left: parent.left
//anchors.horizontalCenter: parent.horizontalCenter
anchors.top: checkForUpdatesSetting.bottom
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.settings.children[1].checked = checked
}
}
CheckBox {
id: enableLatexSetting
anchors.left: parent.left
//anchors.horizontalCenter: parent.horizontalCenter
anchors.top: resetRedoStackSetting.bottom
checked: Helper.getSettingBool("enable_latex")
text: qsTr('Enable LaTeX rendering')
onClicked: {
Helper.setSettingBool("enable_latex", checked)
appMenu.settings.children[2].checked = checked
}
}
CheckBox {
id: autocloseFormulaSetting
anchors.left: parent.left
//anchors.horizontalCenter: parent.horizontalCenter
anchors.top: enableLatexSetting.bottom
checked: Helper.getSettingBool("expression_editor.autoclose")
text: qsTr('Automatically close parenthesises and brackets in expressions')
onClicked: {
Helper.setSettingBool("expression_editor.autoclose", checked)
appMenu.settings.children[3].children[0].checked = checked
}
}
CheckBox {
id: colorizeFormulaSetting
anchors.left: parent.left
//anchors.horizontalCenter: parent.horizontalCenter
anchors.top: autocloseFormulaSetting.bottom
checked: Helper.getSettingBool("expression_editor.colorize")
text: qsTr('Enable syntax highlighting for expressions')
onClicked: {
Helper.setSettingBool("expression_editor.colorize", checked)
appMenu.settings.children[3].children[1].checked = checked
}
}
CheckBox {
id: autocompleteFormulaSetting
anchors.left: parent.left
//anchors.horizontalCenter: parent.horizontalCenter
anchors.top: colorizeFormulaSetting.bottom
checked: Helper.getSettingBool("autocompletion.enabled")
text: qsTr('Enable autocompletion interface in expression editor')
onClicked: {
Helper.setSettingBool("autocompletion.enabled", checked)
appMenu.settings.children[3].children[2].checked = checked
}
}
Row {
anchors.bottom: parent.bottom
anchors.bottomMargin: 10
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() greetingPopup.open()
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,8 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick.Controls import QtQuick.Controls 2.12
import QtQuick import QtQuick 2.12
/*! /*!
\qmltype InsertCharacter \qmltype InsertCharacter
@ -31,19 +31,8 @@ Popup {
signal selected(string character) signal selected(string character)
/*!
\qmlproperty string InsertCharacter::category
Type of special character to insert.
Possible values:
- expression
- domain
- name
- all
*/
property string category: 'all'
width: 280 width: 280
height: Math.ceil(insertGrid.insertChars.length/insertGrid.columns)*(width/insertGrid.columns)+5 height: insertGrid.insertChars.length/insertGrid.columns*(width/insertGrid.columns)
modal: true modal: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
@ -52,40 +41,17 @@ Popup {
width: parent.width width: parent.width
columns: 7 columns: 7
property var insertCharsExpression: [ property var insertChars: [
"∞","π","¹","²","³","⁴","⁵",
"⁶","⁷","⁸","⁹","⁰"
]
property var insertCharsDomain: [
"∅","","∩","","","","",
"⁺","⁻",...insertCharsExpression
]
property var insertCharsName: [
"α","β","γ","δ","ε","ζ","η", "α","β","γ","δ","ε","ζ","η",
"π","θ","κ","λ","μ","ξ","ρ", "π","θ","κ","λ","μ","ξ","ρ",
"ς","σ","τ","φ","χ","ψ","ω", "ς","σ","τ","φ","χ","ψ","ω",
"Γ","Δ","Θ","Λ","Ξ","Π","Σ", "Γ","Δ","Θ","Λ","Ξ","Π","Σ",
"Φ","Ψ","Ω","ₐ","ₑ","ₒ","ₓ", "Φ","Ψ","Ω","ₐ","ₑ","ₒ","ₓ",
"ₕ","ₖ","ₗ","ₘ","ₙ","ₚ","ₛ", "ₕ","ₖ","ₗ","ₘ","ₙ","ₚ","ₛ",
"ₜ","₁","₂","₃","₄","₅","₆", "ₜ","¹","²","³","⁴","⁵","⁶",
"₇","₈","₉","₀" "⁷","⁸","⁹","⁰","₁","₂","₃",
"₄","₅","₆","₇","₈","₉","₀"
] ]
property var insertCharsAll: [
...insertCharsName, ...insertCharsDomain
]
property var insertChars: {
return {
"expression": insertCharsExpression,
"domain": insertCharsDomain,
"name": insertCharsName,
"all": insertCharsAll
}[insertPopup.category]
}
Repeater { Repeater {
model: parent.insertChars.length model: parent.insertChars.length

View file

@ -1,257 +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 QtQuick.Layouts
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import "../js/preferences/common.mjs" as S
import "../js/utils.mjs" as Utils
/*!
\qmltype Preferences
\inqmlmodule eu.ad5001.LogarithmPlotter.Popup
\brief Popup to change global application preferences.
\sa LogarithmPlotter, GreetScreen
*/
Popup {
id: preferencesPopup
x: (parent.width-width)/2
y: Math.max(20, (parent.height-height)/2)
width: settingPopupRow.width + 30
height: settingPopupRow.height + 20
modal: true
focus: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
// Components for the preferences
Component {
id: boolSettingComponent
CheckBox {
height: 20
text: setting.displayName
checked: setting.value()
onClicked: setting.set(this.checked)
}
}
Component {
id: enumIntSettingComponent
// Setting when selecting data from an enum, or an object of a certain type.
Setting.ComboBoxSetting {
height: 30
label: setting.displayName
icon: `settings/${setting.icon}.svg`
currentIndex: setting.value()
model: setting.values
onActivated: function(newIndex) { setting.set(newIndex) }
}
}
Component {
id: stringSettingComponent
Setting.ComboBoxSetting {
height: 30
label: setting.displayName
icon: `settings/${setting.icon}.svg`
editable: true
currentIndex: find(setting.value())
model: setting.defaultValues
onAccepted: function() {
editText = Utils.parseName(editText, false)
if(find(editText) === -1) model.append(editText)
setting.set(editText)
}
onActivated: function(selectedId) {
setting.set(model[selectedId])
}
Component.onCompleted: editText = setting.value()
}
}
Component {
id: numberSettingComponent
Setting.TextSetting {
height: 30
isDouble: true
label: setting.displayName
min: setting.min()
icon: `settings/${setting.icon}.svg`
value: setting.value()
onChanged: function(newValue) {
if(newValue < setting.max())
setting.set(newValue)
else {
value = setting.max()
setting.set(setting.max())
}
}
}
}
Component {
id: expressionSettingComponent
Setting.ExpressionEditor {
height: 30
label: setting.displayName
icon: `settings/${setting.icon}.svg`
defValue: Utils.simplifyExpression(setting.value())
variables: setting.variables
allowGraphObjects: false
property string propertyName: setting.displayName
onChanged: function(newExpr) {
try {
setting.set(newExpr)
} catch(e) {
errorDialog.showDialog(propertyName, newExpr, e.message)
}
}
}
}
Row {
id: settingPopupRow
height: 300
width: categories.width + categorySeparator.width + settingView.width + 70
spacing: 15
anchors {
top: parent.top
bottom: parent.bottom
left: parent.left
right: parent.right
topMargin: 10
bottomMargin: 10
rightMargin: 15
leftMargin: 15
}
ColumnLayout {
id: categories
width: 150
height: parent.height
spacing: 0
clip: true
Repeater {
model: Object.keys(Modules.Preferences.categories)
Button {
// width: 150
Layout.fillWidth: true
text: qsTranslate('settingCategory', modelData)
onClicked: {
settingView.model = Modules.Preferences.categories[modelData]
settingView.name = text
}
}
}
Item {
Layout.fillHeight: true
Layout.fillWidth: true
Button {
id: closeButton
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
text: qsTr('Close')
onClicked: preferencesPopup.close()
}
}
}
Rectangle {
id: categorySeparator
anchors {
top: parent.top
topMargin: 5
}
opacity: 0.3
color: sysPalette.windowText
height: parent.height - 10
width: 1
}
ListView {
id: settingView
clip: true
width: 500
spacing: 10
model: Modules.Preferences.categories.general
anchors {
top: parent.top
bottom: parent.bottom
}
ScrollBar.vertical: ScrollBar { }
property string name: qsTranslate('settingCategory', 'general')
header: Text {
id: settingCategoryName
font.pixelSize: 32
height: 48
color: sysPalette.windowText
text: settingView.name
Rectangle {
id: bottomSeparator
anchors.bottom: parent.bottom
anchors.bottomMargin: 8
opacity: 0.3
color: sysPalette.windowText
width: settingView.width
height: 1
}
}
delegate: Component {
Loader {
width: settingView.width * 2 / 3
property var setting: modelData
sourceComponent: {
if(setting instanceof S.BoolSetting)
return boolSettingComponent
else if(setting instanceof S.EnumIntSetting)
return enumIntSettingComponent
else if(setting instanceof S.NumberSetting)
return numberSettingComponent
else if(setting instanceof S.ExpressionSetting)
return expressionSettingComponent
else if(setting instanceof S.StringSetting)
return stringSettingComponent
else
console.log('Unknown setting type!', modelData.constructor)
}
}
}
}
}
// Component.onCompleted: open()
}

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,9 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick import QtQuick 2.12
import QtQuick.Dialogs import QtQuick.Dialogs 1.3 as D
import QtQuick.Controls import QtQuick.Controls 2.12
/*! /*!
\qmltype ThanksTo \qmltype ThanksTo
@ -27,24 +27,14 @@ import QtQuick.Controls
\sa LogarithmPlotter \sa LogarithmPlotter
*/ */
BaseDialog { D.Dialog {
id: about id: about
title: qsTr("Thanks and Contributions - LogarithmPlotter") title: qsTr("Thanks and Contributions - LogarithmPlotter")
width: 450 width: 400
minimumHeight: 710 height: 600
Column { Column {
anchors { anchors.fill: parent
top: parent.top;
left: parent.left;
bottom: parent.bottom;
right: parent.right;
topMargin: margin;
leftMargin: margin;
bottomMargin: margin;
rightMargin: margin;
}
spacing: 10 spacing: 10
ListView { ListView {
@ -53,7 +43,6 @@ BaseDialog {
width: parent.width width: parent.width
//height: parent.height //height: parent.height
implicitHeight: contentItem.childrenRect.height implicitHeight: contentItem.childrenRect.height
interactive: false
model: ListModel { model: ListModel {
Component.onCompleted: { Component.onCompleted: {
@ -136,7 +125,6 @@ BaseDialog {
model: authors model: authors
width: parent.width - 10 width: parent.width - 10
implicitHeight: contentItem.childrenRect.height implicitHeight: contentItem.childrenRect.height
interactive: false
delegate: Item { delegate: Item {
id: libAuthor id: libAuthor
@ -193,7 +181,6 @@ BaseDialog {
anchors.left: parent.left anchors.left: parent.left
width: parent.width width: parent.width
implicitHeight: contentItem.childrenRect.height implicitHeight: contentItem.childrenRect.height
interactive: false
spacing: 3 spacing: 3
@ -295,7 +282,6 @@ BaseDialog {
model: authors model: authors
width: parent.width - 10 width: parent.width - 10
implicitHeight: contentItem.childrenRect.height implicitHeight: contentItem.childrenRect.height
interactive: false
delegate: Item { delegate: Item {
id: tranAuthor id: tranAuthor

View file

@ -1,6 +1,5 @@
module eu.ad5001.LogarithmPlotter.Popup module eu.ad5001.LogarithmPlotter.Popup
BaseDialog 1.0 BaseDialog.qml
About 1.0 About.qml About 1.0 About.qml
Alert 1.0 Alert.qml Alert 1.0 Alert.qml
FileDialog 1.0 FileDialog.qml FileDialog 1.0 FileDialog.qml
@ -8,4 +7,3 @@ GreetScreen 1.0 GreetScreen.qml
Changelog 1.0 Changelog.qml Changelog 1.0 Changelog.qml
ThanksTo 1.0 ThanksTo.qml ThanksTo 1.0 ThanksTo.qml
InsertCharacter 1.0 InsertCharacter.qml InsertCharacter 1.0 InsertCharacter.qml
Preferences 1.0 Preferences.qml

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,8 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick import QtQuick 2.12
import QtQuick.Controls import QtQuick.Controls 2.12
/*! /*!
\qmltype AutocompletionCategory \qmltype AutocompletionCategory
@ -84,12 +84,11 @@ ListView {
Text { Text {
leftPadding: 5 leftPadding: 5
text: listFiltered.category text: listFiltered.category
color: sysPalette.windowText
} }
Rectangle { Rectangle {
height: 1 height: 1
color: 'gray' color: 'black'
width: parent.width width: parent.width
} }
} }
@ -113,11 +112,9 @@ ListView {
Text { Text {
id: annotationText id: annotationText
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
topPadding: 2 topPadding: 2
bottomPadding: 2 bottomPadding: 2
rightPadding: 15 rightPadding: 15
font.pixelSize: autocompleteText.font.pixelSize - 2
text: listFiltered.model[index].annotation text: listFiltered.model[index].annotation
color: parent.selected ? sysPaletteIn.highlightedText : sysPaletteIn.windowText color: parent.selected ? sysPaletteIn.highlightedText : sysPaletteIn.windowText
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,8 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick import QtQuick 2.12
import QtQuick.Controls import QtQuick.Controls 2.12
/*! /*!
\qmltype ComboBoxSetting \qmltype ComboBoxSetting
@ -114,7 +114,6 @@ Item {
anchors.left: iconLabel.right anchors.left: iconLabel.right
anchors.leftMargin: icon == "" ? 0 : 5 anchors.leftMargin: icon == "" ? 0 : 5
height: 30 height: 30
width: Math.max(85, implicitWidth)
anchors.top: parent.top anchors.top: parent.top
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
text: qsTranslate("control", "%1: ").arg(control.label) text: qsTranslate("control", "%1: ").arg(control.label)

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,13 +16,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick.Controls import QtQuick.Controls 2.12
import QtQuick import QtQuick 2.12
import Qt.labs.platform as Native import QtQuick.Dialogs 1.3 as D
import eu.ad5001.LogarithmPlotter.Popup 1.0 as P import eu.ad5001.LogarithmPlotter.Popup 1.0 as P
import "../js/mathlib.mjs" as MathLib import "../js/mathlib.js" as MathLib
import "../js/utils.mjs" as Utils import "../js/utils.js" as Utils
import "../js/parsing/parsing.mjs" as Parsing import "../js/objects.js" as Objects
import "../js/parsing/parsing.js" as Parsing
/*! /*!
@ -80,17 +81,6 @@ Item {
Icon path of the editor. Icon path of the editor.
*/ */
property string icon: "" property string icon: ""
/*!
\qmlproperty bool ExpressionEditor::allowGraphObjects
If true, allows graph objects to be used as part of the expression.
*/
property bool allowGraphObjects: true
/*!
\qmlproperty var ExpressionEditor::errorDialog
Allows to summon the error dialog when using additional external parsing.
*/
readonly property alias errorDialog: parsingErrorDialog
/*! /*!
\qmlproperty string ExpressionEditor::openAndCloseMatches \qmlproperty string ExpressionEditor::openAndCloseMatches
@ -105,74 +95,19 @@ Item {
} }
/*! /*!
\qmlproperty string ExpressionEditor::colorSchemes \qmlproperty string ExpressionEditor::colorScheme
Color schemes of the editor. Color scheme of the editor, currently based on Breeze Light.
TODO: Make it configurable.
*/ */
readonly property var colorSchemes: [ readonly property var colorScheme: {
{ // Breeze Light 'NORMAL': "#1F1C1B",
'NORMAL': "#1F1C1B", 'VARIABLE': "#0057AE",
'VARIABLE': "#0057AE", 'CONSTANT': "#5E2F00",
'CONSTANT': "#006E28", 'FUNCTION': "#644A9B",
'FUNCTION': "#644A9B", 'OPERATOR': "#A44EA4",
'OPERATOR': "#CA60CA", 'STRING': "#9C0E0E",
'STRING': "#BF0303", 'NUMBER': "#805C00"
'NUMBER': "#B08000" }
},
{ // Breeze Dark
'NORMAL': "#CFCFC2",
'VARIABLE': "#2980B9",
'CONSTANT': "#27AE60",
'FUNCTION': "#8E44AD",
'OPERATOR': "#A44EA4",
'STRING': "#F44F4F",
'NUMBER': "#F67400"
},
{ // Solarized
'NORMAL': "#839496",
'VARIABLE': "#B58900",
'CONSTANT': "#859900",
'FUNCTION': "#268BD2",
'OPERATOR': "#859900",
'STRING': "#2AA198",
'NUMBER': "#2AA198"
},
{ // GitHub Light
'NORMAL': "#24292E",
'VARIABLE': "#D73A49",
'CONSTANT': "#6F42C1",
'FUNCTION': "#6F42C1",
'OPERATOR': "#24292E",
'STRING': "#032F62",
'NUMBER': "#005CC5"
},
{ // GitHub Dark
'NORMAL': "#E1E4E8",
'VARIABLE': "#F97583",
'CONSTANT': "#B392f0",
'FUNCTION': "#B392f0",
'OPERATOR': "#E1E4E8",
'STRING': "#9ECBFF",
'NUMBER': "#79B8FF"
},
{ // Nord
'NORMAL': "#D8DEE9",
'VARIABLE': "#81A1C1",
'CONSTANT': "#8FBCBB",
'FUNCTION': "#88C0D0",
'OPERATOR': "#81A1C1",
'STRING': "#A3BE8C",
'NUMBER': "#B48EAD"
},
{ // Monokai
'NORMAL': "#F8F8F2",
'VARIABLE': "#66D9EF",
'CONSTANT': "#F92672",
'FUNCTION': "#A6E22E",
'OPERATOR': "#F8F8F2",
'STRING': "#E6DB74",
'NUMBER': "#AE81FF"
}
]
Icon { Icon {
id: iconLabel id: iconLabel
@ -188,16 +123,15 @@ Item {
id: labelItem id: labelItem
anchors.left: iconLabel.right anchors.left: iconLabel.right
anchors.leftMargin: icon == "" ? 0 : 5 anchors.leftMargin: icon == "" ? 0 : 5
anchors.top: parent.top
height: parent.height height: parent.height
width: Math.max(85, implicitWidth) anchors.top: parent.top
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
//color: sysPalette.windowText //color: sysPalette.windowText
text: visible ? qsTranslate("control", "%1: ").arg(control.label) : "" text: visible ? qsTranslate("control", "%1: ").arg(control.label) : ""
visible: control.label != "" visible: control.label != ""
} }
Native.MessageDialog { D.MessageDialog {
id: parsingErrorDialog id: parsingErrorDialog
title: qsTranslate("expression", "LogarithmPlotter - Parsing error") title: qsTranslate("expression", "LogarithmPlotter - Parsing error")
text: "" text: ""
@ -207,6 +141,7 @@ Item {
} }
} }
TextField { TextField {
id: editor id: editor
anchors.top: parent.top anchors.top: parent.top
@ -217,7 +152,7 @@ Item {
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
horizontalAlignment: control.label == "" ? TextInput.AlignLeft : TextInput.AlignHCenter horizontalAlignment: control.label == "" ? TextInput.AlignLeft : TextInput.AlignHCenter
text: control.defValue text: control.defValue
color: syntaxHighlightingEnabled ? sysPalette.window : sysPalette.windowText color: syntaxHighlightingEnabled ? "transparent" : sysPalette.windowText
focus: true focus: true
selectByMouse: true selectByMouse: true
@ -240,6 +175,8 @@ Item {
} }
} }
//onTextEdited: acPopupContent.itemSelected = 0
onActiveFocusChanged: { onActiveFocusChanged: {
if(activeFocus && autocompleteEnabled) if(activeFocus && autocompleteEnabled)
autocompletePopup.open() autocompletePopup.open()
@ -247,12 +184,6 @@ Item {
autocompletePopup.close() autocompletePopup.close()
} }
cursorDelegate: Rectangle {
visible: editor.cursorVisible
color: sysPalette.windowText
width: editor.cursorRectangle.width
}
Keys.onUpPressed: function(event) { Keys.onUpPressed: function(event) {
if(autocompleteEnabled) if(autocompleteEnabled)
if(acPopupContent.itemSelected == 0) if(acPopupContent.itemSelected == 0)
@ -273,11 +204,16 @@ Item {
Keys.onPressed: function(event) { Keys.onPressed: function(event) {
// Autocomplete popup events // Autocomplete popup events
//console.log(acPopupContent.currentToken.dot, acPopupContent.previousToken.dot, "@", acPopupContent.currentToken.identifier, acPopupContent.previousToken.identifier, acPopupContent.previousToken2.identifier, objectPropertiesList.objectName, JSON.stringify(objectPropertiesList.baseText), objectPropertiesList.model.length, JSON.stringify(objectPropertiesList.categoryItems))
//console.log("Pressed key:", event.key, Qt.Key_Return, Qt.Key_Enter, event.text, acPopupContent.itemCount)
if(autocompleteEnabled && (event.key == Qt.Key_Enter || event.key == Qt.Key_Return) && acPopupContent.itemCount > 0) { if(autocompleteEnabled && (event.key == Qt.Key_Enter || event.key == Qt.Key_Return) && acPopupContent.itemCount > 0) {
acPopupContent.autocomplete() acPopupContent.autocomplete()
event.accepted = true event.accepted = true
} else } else
acPopupContent.itemSelected = 0 acPopupContent.itemSelected = 0
/*if(event.key == Qt.Key_Left) { // TODO: Don't reset the position when the key moved is still on the same word
if(!acPopupContent.identifierTokenTypes.includes())
}*/
if(event.text in openAndCloseMatches && autoClosing) { if(event.text in openAndCloseMatches && autoClosing) {
@ -392,7 +328,7 @@ Item {
id: objectPropertiesList id: objectPropertiesList
category: qsTr("Object Properties") category: qsTr("Object Properties")
visbilityCondition: control.allowGraphObjects && doesObjectExist visbilityCondition: doesObjectExist
itemStartIndex: 0 itemStartIndex: 0
itemSelected: parent.itemSelected itemSelected: parent.itemSelected
property bool isEnteringProperty: ( property bool isEnteringProperty: (
@ -403,9 +339,9 @@ Item {
property string objectName: isEnteringProperty ? property string objectName: isEnteringProperty ?
(parent.currentToken.dot ? parent.previousToken.value : parent.previousToken2.value) (parent.currentToken.dot ? parent.previousToken.value : parent.previousToken2.value)
: "" : ""
property bool doesObjectExist: isEnteringProperty && (objectName in Modules.Objects.currentObjectsByName) property bool doesObjectExist: isEnteringProperty && objectName in Objects.currentObjectsByName
property var objectProperties: doesObjectExist ? property var objectProperties: doesObjectExist ?
Modules.Objects.currentObjectsByName[objectName].constructor.properties() : Objects.currentObjectsByName[objectName].constructor.properties() :
{} {}
categoryItems: Object.keys(objectProperties) categoryItems: Object.keys(objectProperties)
autocompleteGenerator: (item) => { autocompleteGenerator: (item) => {
@ -444,7 +380,7 @@ Item {
itemSelected: parent.itemSelected itemSelected: parent.itemSelected
categoryItems: Parsing.CONSTANTS_LIST categoryItems: Parsing.CONSTANTS_LIST
autocompleteGenerator: (item) => {return { autocompleteGenerator: (item) => {return {
'text': item, 'annotation': Parsing.CONSTANTS[item], 'text': item, 'annotation': '',
'autocomplete': item + " ", 'cursorFinalOffset': 0 'autocomplete': item + " ", 'cursorFinalOffset': 0
}} }}
baseText: parent.visible ? parent.currentToken.value : "" baseText: parent.visible ? parent.currentToken.value : ""
@ -459,7 +395,7 @@ Item {
itemSelected: parent.itemSelected itemSelected: parent.itemSelected
categoryItems: Parsing.FUNCTIONS_LIST categoryItems: Parsing.FUNCTIONS_LIST
autocompleteGenerator: (item) => {return { autocompleteGenerator: (item) => {return {
'text': item, 'annotation': Parsing.FUNCTIONS_USAGE[item].join(', '), 'text': item, 'annotation': '',
'autocomplete': item+'()', 'cursorFinalOffset': -1 'autocomplete': item+'()', 'cursorFinalOffset': -1
}} }}
baseText: parent.visible ? parent.currentToken.value : "" baseText: parent.visible ? parent.currentToken.value : ""
@ -469,12 +405,12 @@ Item {
id: executableObjectsList id: executableObjectsList
category: qsTr("Executable Objects") category: qsTr("Executable Objects")
visbilityCondition: control.allowGraphObjects && parent.currentToken.identifier && !parent.previousToken.dot visbilityCondition: parent.currentToken.identifier && !parent.previousToken.dot
itemStartIndex: functionsList.itemStartIndex + functionsList.model.length itemStartIndex: functionsList.itemStartIndex + functionsList.model.length
itemSelected: parent.itemSelected itemSelected: parent.itemSelected
categoryItems: Modules.Objects.getObjectsName("ExecutableObject").filter(obj => obj != self) categoryItems: Objects.getObjectsName("ExecutableObject").filter(obj => obj != self)
autocompleteGenerator: (item) => {return { autocompleteGenerator: (item) => {return {
'text': item, 'annotation': Modules.Objects.currentObjectsByName[item] == null ? '' : Objects.currentObjectsByName[item].constructor.displayType(), 'text': item, 'annotation': Objects.currentObjectsByName[item] == null ? '' : Objects.currentObjectsByName[item].constructor.displayType(),
'autocomplete': item+'()', 'cursorFinalOffset': -1 'autocomplete': item+'()', 'cursorFinalOffset': -1
}} }}
baseText: parent.visible ? parent.currentToken.value : "" baseText: parent.visible ? parent.currentToken.value : ""
@ -484,12 +420,12 @@ Item {
id: objectsList id: objectsList
category: qsTr("Objects") category: qsTr("Objects")
visbilityCondition: control.allowGraphObjects && parent.currentToken.identifier && !parent.previousToken.dot visbilityCondition: parent.currentToken.identifier && !parent.previousToken.dot
itemStartIndex: executableObjectsList.itemStartIndex + executableObjectsList.model.length itemStartIndex: executableObjectsList.itemStartIndex + executableObjectsList.model.length
itemSelected: parent.itemSelected itemSelected: parent.itemSelected
categoryItems: Object.keys(Modules.Objects.currentObjectsByName).filter(obj => obj != self) categoryItems: Object.keys(Objects.currentObjectsByName).filter(obj => obj != self)
autocompleteGenerator: (item) => {return { autocompleteGenerator: (item) => {return {
'text': item, 'annotation': `${Modules.Objects.currentObjectsByName[item].constructor.displayType()}`, 'text': item, 'annotation': `${Objects.currentObjectsByName[item].constructor.displayType()}`,
'autocomplete': item+'.', 'cursorFinalOffset': 0 'autocomplete': item+'.', 'cursorFinalOffset': 0
}} }}
baseText: parent.visible ? parent.currentToken.value : "" baseText: parent.visible ? parent.currentToken.value : ""
@ -518,8 +454,6 @@ Item {
x: Math.round((parent.width - width) / 2) x: Math.round((parent.width - width) / 2)
y: Math.round((parent.height - height) / 2) y: Math.round((parent.height - height) / 2)
category: "expression"
onSelected: function(c) { onSelected: function(c) {
editor.insert(editor.cursorPosition, c) editor.insert(editor.cursorPosition, c)
insertPopup.close() insertPopup.close()
@ -549,8 +483,8 @@ Item {
throw new Error(qsTranslate('error', 'Object cannot be dependent on itself.')) throw new Error(qsTranslate('error', 'Object cannot be dependent on itself.'))
// Recursive dependencies // Recursive dependencies
let dependentOnSelfObjects = expr.requiredObjects().filter( let dependentOnSelfObjects = expr.requiredObjects().filter(
(obj) => Modules.Objects.currentObjectsByName[obj].getDependenciesList() (obj) => Objects.currentObjectsByName[obj].getDependenciesList()
.includes(Modules.Objects.currentObjectsByName[control.self]) .includes(Objects.currentObjectsByName[control.self])
) )
if(dependentOnSelfObjects.length == 1) if(dependentOnSelfObjects.length == 1)
throw new Error(qsTranslate('error', 'Circular dependency detected. Object %1 depends on %2.').arg(dependentOnSelfObjects[0].toString()).arg(control.self)) throw new Error(qsTranslate('error', 'Circular dependency detected. Object %1 depends on %2.').arg(dependentOnSelfObjects[0].toString()).arg(control.self))
@ -570,7 +504,7 @@ Item {
Generates a list of tokens from the given. Generates a list of tokens from the given.
*/ */
function tokens(text) { function tokens(text) {
let tokenizer = new Parsing.Tokenizer(new Parsing.Input(text), true, false) let tokenizer = new Parsing.Tokenizer(new Parsing.Input(text), true, true, false)
let tokenList = [] let tokenList = []
let token let token
while((token = tokenizer.next()) != null) while((token = tokenizer.next()) != null)
@ -600,26 +534,25 @@ Item {
*/ */
function colorize(tokenList) { function colorize(tokenList) {
let parsedText = "" let parsedText = ""
let scheme = colorSchemes[Helper.getSettingInt("expression_editor.color_scheme")]
for(let token of tokenList) { for(let token of tokenList) {
switch(token.type) { switch(token.type) {
case Parsing.TokenType.VARIABLE: case Parsing.TokenType.VARIABLE:
parsedText += `<font color="${scheme.VARIABLE}">${token.value}</font>` parsedText += `<font color="${colorScheme.VARIABLE}">${token.value}</font>`
break; break;
case Parsing.TokenType.CONSTANT: case Parsing.TokenType.CONSTANT:
parsedText += `<font color="${scheme.CONSTANT}">${token.value}</font>` parsedText += `<font color="${colorScheme.CONSTANT}">${token.value}</font>`
break; break;
case Parsing.TokenType.FUNCTION: case Parsing.TokenType.FUNCTION:
parsedText += `<font color="${scheme.FUNCTION}">${Utils.escapeHTML(token.value)}</font>` parsedText += `<font color="${colorScheme.FUNCTION}">${Utils.escapeHTML(token.value)}</font>`
break; break;
case Parsing.TokenType.OPERATOR: case Parsing.TokenType.OPERATOR:
parsedText += `<font color="${scheme.OPERATOR}">${Utils.escapeHTML(token.value)}</font>` parsedText += `<font color="${colorScheme.OPERATOR}">${Utils.escapeHTML(token.value)}</font>`
break; break;
case Parsing.TokenType.NUMBER: case Parsing.TokenType.NUMBER:
parsedText += `<font color="${scheme.NUMBER}">${Utils.escapeHTML(token.value)}</font>` parsedText += `<font color="${colorScheme.NUMBER}">${Utils.escapeHTML(token.value)}</font>`
break; break;
case Parsing.TokenType.STRING: case Parsing.TokenType.STRING:
parsedText += `<font color="${scheme.STRING}">${token.limitator}${Utils.escapeHTML(token.value)}${token.limitator}</font>` parsedText += `<font color="${colorScheme.STRING}">${token.limitator}${Utils.escapeHTML(token.value)}${token.limitator}</font>`
break; break;
case Parsing.TokenType.WHITESPACE: case Parsing.TokenType.WHITESPACE:
case Parsing.TokenType.PUNCT: case Parsing.TokenType.PUNCT:

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -15,9 +15,8 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick import QtQuick 2.7
import QtQuick.Window import QtGraphicalEffects 1.0
import QtQuick.Controls.impl
/*! /*!
\qmltype Icon \qmltype Icon
@ -41,16 +40,20 @@ Item {
\qmlproperty string Icon::source \qmlproperty string Icon::source
Path of the icon image source. Path of the icon image source.
*/ */
property alias sourceSize: img.sourceS property alias sourceSize: img.sourceSize.width
ColorImage { Image {
id: img id: img
height: parent.height height: parent.height
width: parent.width width: parent.width
// visible: false //smooth: true
property int sourceS: width*Screen.devicePixelRatio visible: false
sourceSize.width: sourceS sourceSize.width: width*2
sourceSize.height: sourceS sourceSize.height: width*2
}
ColorOverlay {
anchors.fill: img
source: img
color: parent.color color: parent.color
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,9 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick import QtQuick 2.12
import QtQuick.Controls import QtQuick.Controls 2.12
import QtQml.Models import QtQml.Models 2.12
/*! /*!
\qmltype ListSetting \qmltype ListSetting
@ -140,8 +140,8 @@ Column {
visible: control.dictionaryMode visible: control.dictionaryMode
height: parent.height height: parent.height
width: visible ? 50 : 0 width: visible ? 50 : 0
validator: RegularExpressionValidator { validator: RegExpValidator {
regularExpression: control.keyRegexp regExp: control.keyRegexp
} }
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
horizontalAlignment: TextInput.AlignHCenter horizontalAlignment: TextInput.AlignHCenter
@ -180,8 +180,8 @@ Column {
id: valueInput id: valueInput
height: parent.height height: parent.height
width: parent.width - x - deleteButton.width - 5 width: parent.width - x - deleteButton.width - 5
validator: RegularExpressionValidator { validator: RegExpValidator {
regularExpression: control.valueRegexp regExp: control.valueRegexp
} }
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
horizontalAlignment: TextInput.AlignHCenter horizontalAlignment: TextInput.AlignHCenter

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,8 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick.Controls import QtQuick.Controls 2.12
import QtQuick import QtQuick 2.12
import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup
/*! /*!
@ -49,12 +49,6 @@ Item {
If true, the input is being parsed an double before being emitting the \a changed signal. If true, the input is being parsed an double before being emitting the \a changed signal.
*/ */
property bool isDouble: false property bool isDouble: false
/*!
\qmlproperty bool TextSetting::category
Type of special character to insert from the popup.
\sa InsertCharacter::category
*/
property alias category: insertPopup.category
/*! /*!
\qmlproperty double TextSetting::min \qmlproperty double TextSetting::min
Minimum value for numbers that can be entered into the input. Minimum value for numbers that can be entered into the input.
@ -100,14 +94,14 @@ Item {
id: labelItem id: labelItem
anchors.left: iconLabel.right anchors.left: iconLabel.right
anchors.leftMargin: icon == "" ? 0 : 5 anchors.leftMargin: icon == "" ? 0 : 5
anchors.top: parent.top
height: parent.height height: parent.height
width: Math.max(85, implicitWidth) anchors.top: parent.top
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
//color: sysPalette.windowText //color: sysPalette.windowText
text: visible ? qsTranslate("control", "%1: ").arg(control.label) : "" text: visible ? qsTranslate("control", "%1: ").arg(control.label) : ""
visible: control.label != "" visible: control.label != ""
} }
TextField { TextField {
id: input id: input
@ -119,19 +113,17 @@ Item {
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
horizontalAlignment: control.label == "" ? TextInput.AlignLeft : TextInput.AlignHCenter horizontalAlignment: control.label == "" ? TextInput.AlignLeft : TextInput.AlignHCenter
color: sysPalette.windowText color: sysPalette.windowText
validator: RegularExpressionValidator { validator: RegExpValidator {
regularExpression: control.isInt ? /-?[0-9]+/ : control.isDouble ? /-?[0-9]+(\.[0-9]+)?/ : /.+/ regExp: control.isInt ? /-?[0-9]+/ : control.isDouble ? /-?[0-9]+(\.[0-9]+)?/ : /.+/
} }
focus: true focus: true
text: control.defValue text: control.defValue
selectByMouse: true selectByMouse: true
onEditingFinished: function() { onEditingFinished: {
if(insertButton.focus || insertPopup.focus) return if(insertButton.focus || insertPopup.focus) return
var value = text var value = text
if(control.isInt) if(control.isInt) value = Math.max(control.min,parseInt(value).toString()=="NaN"?control.min:parseInt(value))
value = isNaN(parseInt(value)) ? control.min : Math.max(control.min,parseInt(value)) if(control.isDouble) value = Math.max(control.min,parseFloat(value).toString()=="NaN"?control.min:parseFloat(value))
if(control.isDouble)
value = isNaN(parseFloat(value)) ? control.min : Math.max(control.min,parseFloat(value))
if(value != "" && value.toString() != defValue) { if(value != "" && value.toString() != defValue) {
control.changed(value) control.changed(value)
defValue = value.toString() defValue = value.toString()

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,11 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick import QtQuick.Controls 2.12
import QtQuick.Controls import QtQuick 2.12
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup
import "js/utils.mjs" as Utils import "js/utils.js" as Utils
/*! /*!
\qmltype Settings \qmltype Settings
@ -44,93 +44,89 @@ ScrollView {
Zoom on the x axis of the diagram, provided from settings. Zoom on the x axis of the diagram, provided from settings.
\sa Settings \sa Settings
*/ */
property double xzoom: Helper.getSettingInt('default_graph.xzoom') property double xzoom: 100
/*! /*!
\qmlproperty double Settings::yzoom \qmlproperty double Settings::yzoom
Zoom on the y axis of the diagram, provided from settings. Zoom on the y axis of the diagram, provided from settings.
\sa Settings \sa Settings
*/ */
property double yzoom: Helper.getSettingInt('default_graph.yzoom') property double yzoom: 10
/*! /*!
\qmlproperty double Settings::xmin \qmlproperty double Settings::xmin
Minimum x of the diagram, provided from settings. Minimum x of the diagram, provided from settings.
\sa Settings \sa Settings
*/ */
property double xmin: Helper.getSettingInt('default_graph.xmin') property double xmin: 5/10
/*! /*!
\qmlproperty double Settings::ymax \qmlproperty double Settings::ymax
Maximum y of the diagram, provided from settings. Maximum y of the diagram, provided from settings.
\sa Settings \sa Settings
*/ */
property double ymax: Helper.getSettingInt('default_graph.ymax') property double ymax: 25
/*! /*!
\qmlproperty string Settings::xaxisstep \qmlproperty string Settings::xaxisstep
Step of the x axis graduation, provided from settings. Step of the x axis graduation, provided from settings.
\note: Only available in non-logarithmic mode. \note: Only available in non-logarithmic mode.
\sa Settings \sa Settings
*/ */
property string xaxisstep: Helper.getSetting('default_graph.xaxisstep') property string xaxisstep: "4"
/*! /*!
\qmlproperty string Settings::yaxisstep \qmlproperty string Settings::yaxisstep
Step of the y axis graduation, provided from settings. Step of the y axis graduation, provided from settings.
\sa Settings \sa Settings
*/ */
property string yaxisstep: Helper.getSetting('default_graph.yaxisstep') property string yaxisstep: "4"
/*! /*!
\qmlproperty string Settings::xlabel \qmlproperty string Settings::xlabel
Label used on the x axis, provided from settings. Label used on the x axis, provided from settings.
\sa Settings \sa Settings
*/ */
property string xlabel: Helper.getSetting('default_graph.xlabel') property string xlabel: ""
/*! /*!
\qmlproperty string Settings::ylabel \qmlproperty string Settings::ylabel
Label used on the y axis, provided from settings. Label used on the y axis, provided from settings.
\sa Settings \sa Settings
*/ */
property string ylabel: Helper.getSetting('default_graph.ylabel') property string ylabel: ""
/*! /*!
\qmlproperty double Settings::linewidth \qmlproperty double Settings::linewidth
Width of lines that will be drawn into the canvas, provided from settings. Width of lines that will be drawn into the canvas, provided from settings.
\sa Settings \sa Settings
*/ */
property double linewidth: Helper.getSettingInt('default_graph.linewidth') property double linewidth: 1
/*! /*!
\qmlproperty double Settings::textsize \qmlproperty double Settings::textsize
Font size of the text that will be drawn into the canvas, provided from settings. Font size of the text that will be drawn into the canvas, provided from settings.
\sa Settings \sa Settings
*/ */
property double textsize: Helper.getSettingInt('default_graph.textsize') property double textsize: 18
/*! /*!
\qmlproperty bool Settings::logscalex \qmlproperty bool Settings::logscalex
true if the canvas should be in logarithmic mode, false otherwise. true if the canvas should be in logarithmic mode, false otherwise.
Provided from settings. Provided from settings.
\sa Settings \sa Settings
*/ */
property bool logscalex: Helper.getSettingBool('default_graph.logscalex') property bool logscalex: true
/*! /*!
\qmlproperty bool Settings::showxgrad \qmlproperty bool Settings::showxgrad
true if the x graduation should be shown, false otherwise. true if the x graduation should be shown, false otherwise.
Provided from settings. Provided from settings.
\sa Settings \sa Settings
*/ */
property bool showxgrad: Helper.getSettingBool('default_graph.showxgrad') property bool showxgrad: true
/*! /*!
\qmlproperty bool Settings::showygrad \qmlproperty bool Settings::showygrad
true if the y graduation should be shown, false otherwise. true if the y graduation should be shown, false otherwise.
Provided from settings. Provided from settings.
\sa Settings \sa Settings
*/ */
property bool showygrad: Helper.getSettingBool('default_graph.showygrad') property bool showygrad: true
/*! /*!
\qmlproperty bool Settings::saveFilename \qmlproperty bool Settings::saveFilename
Path of the currently opened file. Empty if no file is opened. Path of the currently opened file. Empty if no file is opened.
*/ */
property string saveFilename: "" property string saveFilename: ""
Component.onCompleted: {
Modules.IO.initialize(root, settings, alert)
}
Column { Column {
spacing: 10 spacing: 10
width: parent.width width: parent.width
@ -139,12 +135,12 @@ ScrollView {
Popup.FileDialog { Popup.FileDialog {
id: fdiag id: fdiag
onAccepted: { onAccepted: {
var filePath = fdiag.currentFile.toString().substr(7) var filePath = fileUrl.toString().substr(7)
settings.saveFilename = filePath settings.saveFilename = filePath
if(exportMode) { if(exportMode) {
Modules.IO.saveDiagram(filePath) root.saveDiagram(filePath)
} else { } else {
Modules.IO.loadDiagram(filePath) root.loadDiagram(filePath)
if(xAxisLabel.find(settings.xlabel) == -1) xAxisLabel.model.append({text: settings.xlabel}) if(xAxisLabel.find(settings.xlabel) == -1) xAxisLabel.model.append({text: settings.xlabel})
xAxisLabel.editText = settings.xlabel xAxisLabel.editText = settings.xlabel
if(yAxisLabel.find(settings.ylabel) == -1) yAxisLabel.model.append({text: settings.ylabel}) if(yAxisLabel.find(settings.ylabel) == -1) yAxisLabel.model.append({text: settings.ylabel})
@ -159,7 +155,7 @@ ScrollView {
height: 30 height: 30
isDouble: true isDouble: true
label: qsTr("X Zoom") label: qsTr("X Zoom")
min: 0.1 min: 1
icon: "settings/xzoom.svg" icon: "settings/xzoom.svg"
width: settings.settingWidth width: settings.settingWidth
value: settings.xzoom.toFixed(2) value: settings.xzoom.toFixed(2)
@ -173,7 +169,6 @@ ScrollView {
id: zoomY id: zoomY
height: 30 height: 30
isDouble: true isDouble: true
min: 0.1
label: qsTr("Y Zoom") label: qsTr("Y Zoom")
icon: "settings/yzoom.svg" icon: "settings/yzoom.svg"
width: settings.settingWidth width: settings.settingWidth
@ -227,10 +222,10 @@ ScrollView {
label: qsTr("Max X") label: qsTr("Max X")
icon: "settings/xmax.svg" icon: "settings/xmax.svg"
width: settings.settingWidth width: settings.settingWidth
defValue: canvas.px2x(canvas.width).toFixed(2) value: canvas.px2x(canvas.canvasSize.width).toFixed(2)
onChanged: function(xvaluemax) { onChanged: function(xvaluemax) {
if(xvaluemax > settings.xmin) { if(xvaluemax > settings.xmin) {
settings.xzoom = settings.xzoom * canvas.width/(canvas.x2px(xvaluemax)) // Adjusting zoom to fit. = (end)/(px of current point) settings.xzoom = settings.xzoom * canvas.canvasSize.width/(canvas.x2px(xvaluemax)) // Adjusting zoom to fit. = (end)/(px of current point)
settings.changed() settings.changed()
} else { } else {
alert.show("Maximum x value must be superior to minimum.") alert.show("Maximum x value must be superior to minimum.")
@ -246,10 +241,10 @@ ScrollView {
label: qsTr("Min Y") label: qsTr("Min Y")
icon: "settings/ymin.svg" icon: "settings/ymin.svg"
width: settings.settingWidth width: settings.settingWidth
defValue: canvas.px2y(canvas.height).toFixed(2) defValue: canvas.px2y(canvas.canvasSize.height).toFixed(2)
onChanged: function(yvaluemin) { onChanged: function(yvaluemin) {
if(yvaluemin < settings.ymax) { if(yvaluemin < settings.ymax) {
settings.yzoom = settings.yzoom * canvas.height/(canvas.y2px(yvaluemin)) // Adjusting zoom to fit. = (end)/(px of current point) settings.yzoom = settings.yzoom * canvas.canvasSize.height/(canvas.y2px(yvaluemin)) // Adjusting zoom to fit. = (end)/(px of current point)
settings.changed() settings.changed()
} else { } else {
alert.show("Minimum y value must be inferior to maximum.") alert.show("Minimum y value must be inferior to maximum.")
@ -260,7 +255,6 @@ ScrollView {
Setting.TextSetting { Setting.TextSetting {
id: xAxisStep id: xAxisStep
height: 30 height: 30
category: "expression"
label: qsTr("X Axis Step") label: qsTr("X Axis Step")
icon: "settings/xaxisstep.svg" icon: "settings/xaxisstep.svg"
width: settings.settingWidth width: settings.settingWidth
@ -275,7 +269,6 @@ ScrollView {
Setting.TextSetting { Setting.TextSetting {
id: yAxisStep id: yAxisStep
height: 30 height: 30
category: "expression"
label: qsTr("Y Axis Step") label: qsTr("Y Axis Step")
icon: "settings/yaxisstep.svg" icon: "settings/yaxisstep.svg"
width: settings.settingWidth width: settings.settingWidth
@ -447,7 +440,7 @@ ScrollView {
if(settings.saveFilename == "") { if(settings.saveFilename == "") {
saveAs() saveAs()
} else { } else {
Modules.IO.saveDiagram(settings.saveFilename) root.saveDiagram(settings.saveFilename)
} }
} }

View file

@ -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/>.
*/
import QtQuick
import QtQuick.Controls
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import "js/mathlib.mjs" as MathLib
import "js/historylib.mjs" as HistoryLib
/*!
\qmltype ViewPositionChangeOverlay
\inqmlmodule eu.ad5001.LogarithmPlotter
\brief Overlay used allow the user to drag the canvas' position and change the zoom level.
Provides an overlay over the canvas that detects mouse movements and changes the canvas view position
accordingly by providing new signals.
\sa LogarithmPlotter, LogGraphCanvas, Settings
*/
Item {
id: viewChangeRoot
visible: true
clip: true
/*!
\qmlsignal ViewPositionChangeOverlay::positionChanged(int deltaX, int deltaY)
Emmited when the user dragged the canvas and the view should be refreshed.
The corresponding handler is \c onPositionChanged.
*/
signal positionChanged(int deltaX, int deltaY)
/*!
\qmlsignal ViewPositionChangeOverlay::beginPositionChange()
Emmited when the user starts dragging the canvas.
The corresponding handler is \c onBeginPositionChange.
*/
signal beginPositionChange()
/*!
\qmlsignal ViewPositionChangeOverlay::endPositionChange(int deltaX, int deltaY)
Emmited when the user stops dragging the canvas.
The corresponding handler is \c onEndPositionChange.
*/
signal endPositionChange(int deltaX, int deltaY)
/*!
\qmlproperty var ViewPositionChangeOverlay::canvas
LogGraphCanvas instance.
*/
property var canvas
/*!
\qmlproperty var ViewPositionChangeOverlay::settingsInstance
Settings instance.
*/
property var settingsInstance
/*!
\qmlproperty int ViewPositionChangeOverlay::prevX
The x coordinate (on the mousearea) at the last change of the canvas position.
*/
property int prevX
/*!
\qmlproperty int ViewPositionChangeOverlay::prevY
The y coordinate (on the mousearea) at the last change of the canvas position.
*/
property int prevY
/*!
\qmlproperty double ViewPositionChangeOverlay::baseZoomMultiplier
How much should the zoom be mutliplied/scrolled by for one scroll step (120° on the mouse wheel).
*/
property double baseZoomMultiplier: 0.1
MouseArea {
id: dragArea
anchors.fill: parent
cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor
property int positionChangeTimer: 0
function updatePosition(deltaX, deltaY) {
settingsInstance.xmin = (Modules.Canvas.px2x(Modules.Canvas.x2px(settingsInstance.xmin)-deltaX))
settingsInstance.ymax += deltaY/canvas.yzoom
settingsInstance.ymax = settingsInstance.ymax.toFixed(4)
settingsInstance.changed()
parent.positionChanged(deltaX, deltaY)
}
onPressed: function(mouse) {
prevX = mouse.x
prevY = mouse.y
parent.beginPositionChange()
}
onPositionChanged: function(mouse) {
positionChangeTimer++
if(positionChangeTimer == 3) {
let deltaX = mouse.x - prevX
let deltaY = mouse.y - prevY
updatePosition(deltaX, deltaY)
prevX = mouse.x
prevY = mouse.y
positionChangeTimer = 0
}
}
onReleased: function(mouse) {
let deltaX = mouse.x - prevX
let deltaY = mouse.y - prevY
updatePosition(deltaX, deltaY)
parent.endPositionChange(deltaX, deltaY)
}
onWheel: function(wheel) {
// Scrolling
let scrollSteps = Math.round(wheel.angleDelta.y / 120)
let zoomMultiplier = Math.pow(1+baseZoomMultiplier, Math.abs(scrollSteps))
// Avoid floating-point rounding errors by removing the zoom *after*
let xZoomDelta = (settingsInstance.xzoom*zoomMultiplier - settingsInstance.xzoom)
let yZoomDelta = (settingsInstance.yzoom*zoomMultiplier - settingsInstance.yzoom)
if(scrollSteps < 0) { // Negative scroll
xZoomDelta *= -1
yZoomDelta *= -1
}
let newXZoom = (settingsInstance.xzoom+xZoomDelta).toFixed(0)
let newYZoom = (settingsInstance.yzoom+yZoomDelta).toFixed(0)
if(newXZoom == settingsInstance.xzoom) // No change, allow more precision.
newXZoom = (settingsInstance.xzoom+xZoomDelta).toFixed(4)
if(newYZoom == settingsInstance.yzoom) // No change, allow more precision.
newYZoom = (settingsInstance.yzoom+yZoomDelta).toFixed(4)
settingsInstance.xzoom = newXZoom
settingsInstance.yzoom = newYZoom
settingsInstance.changed()
}
}
}

View file

@ -1,48 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="24"
height="24"
version="1.1"
id="svg6"
sodipodi:docname="remove.svg"
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs10" />
<sodipodi:namedview
id="namedview8"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="true"
inkscape:zoom="34.458333"
inkscape:cx="12"
inkscape:cy="10.505441"
inkscape:window-width="1920"
inkscape:window-height="1007"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg6">
<inkscape:grid
type="xygrid"
id="grid822" />
</sodipodi:namedview>
<path
id="rect2"
style="fill-rule:evenodd;stroke-width:3.16228"
transform="rotate(135)"
d="M -1.4142136,-26.870058 H 1.4142136 V -7.0710678 H -1.4142136 Z"
sodipodi:nodetypes="ccccc" />
<path
style="fill:#000000;fill-rule:evenodd;stroke-width:3.16228"
d="M 20,6 6,20 4,18 18,4 Z"
id="path4"
sodipodi:nodetypes="ccccc" />
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 251 B

View file

@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 550 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M24 13.616v-3.232l-2.869-1.02c-.198-.687-.472-1.342-.811-1.955l1.308-2.751-2.285-2.285-2.751 1.307c-.613-.339-1.269-.613-1.955-.811l-1.021-2.869h-3.232l-1.021 2.869c-.686.198-1.342.471-1.955.811l-2.751-1.308-2.285 2.285 1.308 2.752c-.339.613-.614 1.268-.811 1.955l-2.869 1.02v3.232l2.869 1.02c.197.687.472 1.342.811 1.955l-1.308 2.751 2.285 2.286 2.751-1.308c.613.339 1.269.613 1.955.811l1.021 2.869h3.232l1.021-2.869c.687-.198 1.342-.472 1.955-.811l2.751 1.308 2.285-2.286-1.308-2.751c.339-.613.613-1.268.811-1.955l2.869-1.02zm-12 2.384c-2.209 0-4-1.791-4-4s1.791-4 4-4 4 1.791 4 4-1.791 4-4 4z"/></svg>

Before

Width:  |  Height:  |  Size: 696 B

View file

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

Before

Width:  |  Height:  |  Size: 19 B

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="24"
height="24"
version="1.1"
id="svg6"
sodipodi:docname="remove.svg"
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs10" />
<sodipodi:namedview
id="namedview8"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="true"
inkscape:zoom="34.458333"
inkscape:cx="12"
inkscape:cy="10.505441"
inkscape:window-width="1920"
inkscape:window-height="1007"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg6">
<inkscape:grid
type="xygrid"
id="grid822" />
</sodipodi:namedview>
<path
id="rect2"
style="fill-rule:evenodd;stroke-width:3.16228"
transform="rotate(135)"
d="M -1.4142136,-26.870058 H 1.4142136 V -7.0710678 H -1.4142136 Z"
sodipodi:nodetypes="ccccc" />
<path
style="fill:#000000;fill-rule:evenodd;stroke-width:3.16228"
d="M 20,6 6,20 4,18 18,4 Z"
id="path4"
sodipodi:nodetypes="ccccc" />
</svg>

Before

Width:  |  Height:  |  Size: 19 B

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -1,17 +1,44 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg <svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24.0px" width="24.0px"
height="24.0px" height="24.0px"
viewBox="0 0 24.0 24.0" viewBox="0 0 24.0 24.0"
version="1.1" version="1.1"
id="SVGRoot" id="SVGRoot"
xmlns="http://www.w3.org/2000/svg" sodipodi:docname="Function.svg"
xmlns:svg="http://www.w3.org/2000/svg" inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs <defs
id="defs833" /> id="defs833" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="31.678384"
inkscape:cx="15.268708"
inkscape:cy="12.238724"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="true"
inkscape:window-width="1920"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid1403" />
</sodipodi:namedview>
<metadata <metadata
id="metadata10"> id="metadata10">
<rdf:RDF> <rdf:RDF>
@ -20,7 +47,8 @@
<dc:format>image/svg+xml</dc:format> <dc:format>image/svg+xml</dc:format>
<dc:type <dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:date>2021-2023</dc:date> <dc:title />
<dc:date>2021</dc:date>
<dc:creator> <dc:creator>
<cc:Agent> <cc:Agent>
<dc:title>Ad5001</dc:title> <dc:title>Ad5001</dc:title>
@ -28,7 +56,7 @@
</dc:creator> </dc:creator>
<dc:rights> <dc:rights>
<cc:Agent> <cc:Agent>
<dc:title>(C) Ad5001 2021-2023 - Licensed under CC4.0-BY-NC-SA</dc:title> <dc:title>(C) Ad5001 2021 - Licensed under CC4.0-BY-NC-SA</dc:title>
</cc:Agent> </cc:Agent>
</dc:rights> </dc:rights>
<cc:license <cc:license
@ -50,46 +78,33 @@
</rdf:RDF> </rdf:RDF>
</metadata> </metadata>
<g <g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1"> id="layer1">
<text <path
xml:space="preserve" id="rect1415"
style="font-style:normal;font-weight:normal;font-size:17.3333px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none" style="fill:#000000;fill-rule:evenodd"
x="0.012050295" d="M 2,9 C 2,5 6,5 6,5 H 7 V 7 H 6 C 6,7 4,7 4,9 v 2 h 2 v 2 H 4 v 5 H 2 V 13 H 0 v -2 h 2 z"
y="17.985596" sodipodi:nodetypes="ccccccccccccccccc" />
id="text1"><tspan <path
id="tspan1" id="rect839"
x="0.012050295" style="fill:#000000;fill-rule:evenodd;stroke-width:2"
y="17.985596" d="m 12,3 v 2 c 0,0 -2,0 -2,7 0,7 2,7 2,7 v 2 C 8,21 8,12 8,12 8,12 8,3 12,3 Z"
style="font-size:17.3333px">f</tspan></text> sodipodi:nodetypes="ccccccc" />
<text <path
xml:space="preserve" id="rect857"
style="font-style:normal;font-weight:normal;font-size:17.3333px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none" style="fill:#000000;fill-rule:evenodd;stroke-width:2"
x="10.913334" d="m 12,10 h 2 l 6,8 h -2 z"
y="18.134649" sodipodi:nodetypes="ccccc" />
id="text1-3"><tspan <path
id="tspan1-6" id="rect857-7"
x="10.913334" style="fill:#000000;fill-rule:evenodd;stroke-width:2"
y="18.134649" d="m 12,18 h 2 l 6,-8 h -2 z"
style="font-size:17.3333px">x</tspan></text> sodipodi:nodetypes="ccccc" />
<text <path
xml:space="preserve" id="rect839-3"
style="font-style:normal;font-weight:normal;font-size:17.3333px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none" style="fill:#000000;fill-rule:evenodd;stroke-width:2"
x="6.3066678" d="m 20,3 v 2 c 0,0 2,0 2,7 0,7 -2,7 -2,7 v 2 c 4,0 4,-9 4,-9 0,0 0,-9 -4,-9 z"
y="17.646639" sodipodi:nodetypes="ccccccc" />
id="text2"><tspan
id="tspan2"
x="6.3066678"
y="17.646639"
style="font-size:17.3333px">(</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:17.3333px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none"
x="18.306667"
y="17.646639"
id="text2-7"><tspan
id="tspan2-5"
x="18.306667"
y="17.646639"
style="font-size:17.3333px">)</tspan></text>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -1,17 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg <svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24.0px" width="24.0px"
height="24.0px" height="24.0px"
viewBox="0 0 24.0 24.0" viewBox="0 0 24.0 24.0"
version="1.1" version="1.1"
id="SVGRoot" id="SVGRoot"
xmlns="http://www.w3.org/2000/svg" sodipodi:docname="Gain Bode.svg"
xmlns:svg="http://www.w3.org/2000/svg" inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs <defs
id="defs1469" /> id="defs1469" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="15.839192"
inkscape:cx="15.763196"
inkscape:cy="7.8365971"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="true"
inkscape:window-width="1920"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid2039" />
<inkscape:grid
type="xygrid"
id="grid2058" />
</sodipodi:namedview>
<metadata <metadata
id="metadata1472"> id="metadata1472">
<rdf:RDF> <rdf:RDF>
@ -20,40 +50,13 @@
<dc:format>image/svg+xml</dc:format> <dc:format>image/svg+xml</dc:format>
<dc:type <dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:date>2021-2023</dc:date> <dc:title />
<dc:creator>
<cc:Agent>
<dc:title>Ad5001</dc:title>
</cc:Agent>
</dc:creator>
<dc:rights>
<cc:Agent>
<dc:title>(C) Ad5001 2021-2023 - Licensed under CC4.0-BY-NC-SA</dc:title>
</cc:Agent>
</dc:rights>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-nc-sa/4.0/" />
</cc:Work> </cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-nc-sa/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:prohibits
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF> </rdf:RDF>
</metadata> </metadata>
<g <g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1"> id="layer1">
<rect <rect
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:14.257;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:14.257;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0"
@ -64,14 +67,15 @@
y="17" /> y="17" />
<text <text
xml:space="preserve" xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:17px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" style="font-style:normal;font-weight:normal;font-size:17.3333px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
x="-0.105" x="-0.12666447"
y="11.959" y="12.134649"
id="text839"><tspan id="text839"><tspan
sodipodi:role="line"
id="tspan837" id="tspan837"
x="-0.105" x="-0.12666447"
y="11.959" y="12.134649"
style="font-size:17px">ω</tspan></text> style="font-size:17.3333px">ω</tspan></text>
<circle <circle
style="fill:#000000;fill-rule:evenodd;stroke-width:2" style="fill:#000000;fill-rule:evenodd;stroke-width:2"
id="path837" id="path837"
@ -82,6 +86,7 @@
id="rect837" id="rect837"
style="fill:#000000;fill-rule:evenodd;stroke-width:2.10035" style="fill:#000000;fill-rule:evenodd;stroke-width:2.10035"
transform="rotate(30)" transform="rotate(30)"
d="m 20.686533,-6.169873 2.232051,-0.1339746 -0.06218,13.8923049 -2.23205,0.1339746 z" /> d="m 20.686533,-6.169873 2.232051,-0.1339746 -0.06218,13.8923049 -2.23205,0.1339746 z"
sodipodi:nodetypes="ccccc" />
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -1,17 +1,44 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg <svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24.0px" width="24.0px"
height="24.0px" height="24.0px"
viewBox="0 0 24.0 24.0" viewBox="0 0 24.0 24.0"
version="1.1" version="1.1"
id="SVGRoot" id="SVGRoot"
xmlns="http://www.w3.org/2000/svg" sodipodi:docname="Phase Bode.svg"
xmlns:svg="http://www.w3.org/2000/svg" inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs <defs
id="defs10" /> id="defs10" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.4"
inkscape:cx="15.347905"
inkscape:cy="8.3727678"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="true"
inkscape:window-width="1920"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid19" />
</sodipodi:namedview>
<metadata <metadata
id="metadata13"> id="metadata13">
<rdf:RDF> <rdf:RDF>
@ -20,40 +47,13 @@
<dc:format>image/svg+xml</dc:format> <dc:format>image/svg+xml</dc:format>
<dc:type <dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:date>2021-2023</dc:date> <dc:title />
<dc:creator>
<cc:Agent>
<dc:title>Ad5001</dc:title>
</cc:Agent>
</dc:creator>
<dc:rights>
<cc:Agent>
<dc:title>(C) Ad5001 2021-2023 - Licensed under CC4.0-BY-NC-SA</dc:title>
</cc:Agent>
</dc:rights>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-nc-sa/4.0/" />
</cc:Work> </cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-nc-sa/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:prohibits
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF> </rdf:RDF>
</metadata> </metadata>
<g <g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1"> id="layer1">
<rect <rect
style="fill:#000000;stroke-width:1.1547" style="fill:#000000;stroke-width:1.1547"
@ -65,7 +65,8 @@
<path <path
id="rect26-3" id="rect26-3"
style="fill:#000000;stroke-width:1.22474" style="fill:#000000;stroke-width:1.22474"
d="m 15,2 v 14 h 2 V 4 h 7 V 2 Z" /> d="m 15,2 v 14 h 2 V 4 h 7 V 2 Z"
sodipodi:nodetypes="ccccccc" />
<circle <circle
style="fill:#000000;stroke-width:2.09999" style="fill:#000000;stroke-width:2.09999"
id="path45" id="path45"
@ -74,13 +75,14 @@
r="4" /> r="4" />
<text <text
xml:space="preserve" xml:space="preserve"
style="font-size:17px;line-height:1.25;font-family:sans-serif;text-align:center;letter-spacing:0px;text-anchor:middle;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none" style="font-size:18px;line-height:1.25;font-family:sans-serif;text-align:center;letter-spacing:0px;text-anchor:middle;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none"
x="6.1339936" x="6.4359932"
y="11.163" y="11.702"
id="text49"><tspan id="text49"><tspan
sodipodi:role="line"
id="tspan47" id="tspan47"
x="6.1339936" x="6.4359932"
y="11.163" y="11.702"
style="font-size:17px;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none">φ</tspan></text> style="font-size:18px;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none">φ</tspan></text>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -1,67 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path style="opacity:1;vector-effect:none;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" d="M9.98 18.12a3.5 3.5 0 0 1-3.064 3.855 3.5 3.5 0 0 1-3.887-3.022 3.5 3.5 0 0 1 2.982-3.919 3.5 3.5 0 0 1 3.95 2.94"/><text xml:space="preserve" style="font-style:normal;font-weight:400;font-size:17.3373px;line-height:1.25;font-family:sans-serif;letter-spacing:0;word-spacing:0;fill-opacity:1;stroke:none;stroke-width:1.00023" x="12.067" y="13.923" transform="scale(.99447 1.00556)"><tspan x="12.067" y="13.923" style="font-size:17.3373px;stroke-width:1.00023">A</tspan></text></svg>
<svg
width="24"
height="24"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs1" />
<path
style="opacity:1;vector-effect:none;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0"
d="M9.98 18.12a3.5 3.5 0 0 1-3.064 3.855 3.5 3.5 0 0 1-3.887-3.022 3.5 3.5 0 0 1 2.982-3.919 3.5 3.5 0 0 1 3.95 2.94"
id="path1" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:400;font-size:17px;line-height:1.25;font-family:sans-serif;letter-spacing:0;word-spacing:0;fill-opacity:1;stroke:none;stroke-width:1.00023"
x="11.964725"
y="13.701941"
transform="scale(0.99447036,1.0055604)"
id="text1"><tspan
x="11.964725"
y="13.701941"
style="font-size:17px;stroke-width:1.00023"
id="tspan1">A</tspan></text>
<metadata
id="metadata1">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:date>2021-2023</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>Ad5001</dc:title>
</cc:Agent>
</dc:creator>
<dc:rights>
<cc:Agent>
<dc:title>(C) Ad5001 2021-2023 - Licensed under CC4.0-BY-NC-SA</dc:title>
</cc:Agent>
</dc:rights>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-nc-sa/4.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-nc-sa/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:prohibits
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 772 B

View file

@ -1,17 +1,45 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg <svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24.0px" width="24.0px"
height="24.0px" height="24.0px"
viewBox="0 0 24.0 24.0" viewBox="0 0 24.0 24.0"
version="1.1" version="1.1"
id="SVGRoot" id="SVGRoot"
xmlns="http://www.w3.org/2000/svg" sodipodi:docname="Sequence.svg"
xmlns:svg="http://www.w3.org/2000/svg" inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs <defs
id="defs10" /> id="defs10" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.4"
inkscape:cx="-1.4929284"
inkscape:cy="9.7261905"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="true"
showguides="true"
inkscape:window-width="1920"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid19" />
</sodipodi:namedview>
<metadata <metadata
id="metadata13"> id="metadata13">
<rdf:RDF> <rdf:RDF>
@ -20,62 +48,56 @@
<dc:format>image/svg+xml</dc:format> <dc:format>image/svg+xml</dc:format>
<dc:type <dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:date>2021-2023</dc:date> <dc:title />
<dc:creator>
<cc:Agent>
<dc:title>Ad5001</dc:title>
</cc:Agent>
</dc:creator>
<dc:rights>
<cc:Agent>
<dc:title>(C) Ad5001 2021-2023 - Licensed under CC4.0-BY-NC-SA</dc:title>
</cc:Agent>
</dc:rights>
</cc:Work> </cc:Work>
</rdf:RDF> </rdf:RDF>
</metadata> </metadata>
<g <g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1"> id="layer1">
<text <text
xml:space="preserve" xml:space="preserve"
style="font-size:17.3333px;line-height:1.25;font-family:sans-serif;text-align:center;letter-spacing:0px;text-anchor:middle;-inkscape-font-specification:sans-serif;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal" style="font-size:17.3333px;line-height:1.25;font-family:sans-serif;text-align:center;letter-spacing:0px;text-anchor:middle"
x="7.483983" x="7.483983"
y="16.134649" y="16.134649"
id="text908"><tspan id="text908"><tspan
sodipodi:role="line"
id="tspan906" id="tspan906"
x="7.483983" x="7.483983"
y="16.134649" y="16.134649"
style="font-size:17.3333px;-inkscape-font-specification:sans-serif;font-family:sans-serif;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal">u</tspan></text> style="font-size:17.3333px">u</tspan></text>
<text <text
xml:space="preserve" xml:space="preserve"
style="font-size:17px;line-height:1.25;font-family:sans-serif;text-align:center;letter-spacing:0px;text-anchor:middle;stroke-width:1.00003;-inkscape-font-specification:sans-serif;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal" style="font-size:17px;line-height:1.25;font-family:sans-serif;text-align:center;letter-spacing:0px;text-anchor:middle;stroke-width:1.00003"
x="16.365566" x="16.365566"
y="18.663307" y="18.663307"
id="text912" id="text912"
transform="scale(1.0000324,0.9999676)"><tspan transform="scale(1.0000324,0.9999676)"><tspan
sodipodi:role="line"
id="tspan910" id="tspan910"
x="16.365566" x="16.365566"
y="18.663307" y="18.663307"
style="font-size:17px;stroke-width:1.00003;-inkscape-font-specification:sans-serif;font-family:sans-serif;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal">n</tspan></text> style="font-size:17px;stroke-width:1.00003">n</tspan></text>
<text <g
xml:space="preserve" aria-label="("
style="font-style:normal;font-weight:normal;font-size:17.3333px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;-inkscape-font-specification:sans-serif;font-stretch:normal;font-variant:normal" id="text852"
x="-0.69333196" style="font-style:normal;font-weight:normal;font-size:12px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.4396;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
y="17.646639" transform="matrix(1.0022756,0,0,1.2616817,-0.26079098,-9.0560687)">
id="text2"><tspan <path
id="tspan2" d="M 2.712,9.86 C 1.536,11.528 0.48,12.956 0.48,15.8 c 0,2.844 1.056,4.272 2.232,5.94 L 3.408,21.26 C 2.328,19.664 1.632,18.368 1.632,15.8 c 0,-2.58 0.696,-3.864 1.776,-5.46 z"
x="-0.69333196" style="font-size:12px;stroke:#000000;stroke-width:0.4396;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
y="17.646639" id="path854" />
style="font-size:17.3333px;-inkscape-font-specification:sans-serif;font-family:sans-serif;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal">(</tspan></text> </g>
<text <g
xml:space="preserve" aria-label="("
style="font-style:normal;font-weight:normal;font-size:17.3333px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;-inkscape-font-specification:sans-serif;font-stretch:normal;font-variant:normal" id="text852-3"
x="18.806667" style="font-style:normal;font-weight:normal;font-size:12px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.4396;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
y="17.646639" transform="matrix(-1.0030304,0,0,1.2658306,24.414952,-9.1000412)">
id="text2-7"><tspan <path
id="tspan2-5" d="M 2.712,9.86 C 1.536,11.528 0.48,12.956 0.48,15.8 c 0,2.844 1.056,4.272 2.232,5.94 L 3.408,21.26 C 2.328,19.664 1.632,18.368 1.632,15.8 c 0,-2.58 0.696,-3.864 1.776,-5.46 z"
x="18.806667" style="font-size:12px;stroke:#000000;stroke-width:0.4396;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
y="17.646639" id="path854-6" />
style="font-size:17.3333px;-inkscape-font-specification:sans-serif;font-family:sans-serif;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal">)</tspan></text> </g>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24.0px"
height="24.0px"
viewBox="0 0 24.0 24.0"
version="1.1"
id="SVGRoot"
sodipodi:docname="Somme gains Bode.svg"
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
<defs
id="defs1469" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="63.356768"
inkscape:cx="15.947723"
inkscape:cy="5.6917309"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="true"
inkscape:window-width="1920"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid2039" />
<inkscape:grid
type="xygrid"
id="grid2058" />
</sodipodi:namedview>
<metadata
id="metadata1472">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1">
<path
id="rect838"
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
d="M 2,2 H 8 V 3 H 4 L 7,6 4,9 h 4 v 1 H 2 V 9 L 5,6 2,3 Z"
sodipodi:nodetypes="ccccccccccccc" />
<rect
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:14.257;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0"
id="rect835"
width="14"
height="2"
x="0"
y="17" />
<circle
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
id="path837"
cx="13"
cy="18"
r="4" />
<path
id="rect837"
style="fill:#000000;fill-rule:evenodd;stroke-width:2.10035"
transform="rotate(30)"
d="m 20.686533,-6.169873 2.232051,-0.1339746 -0.06218,13.8923049 -2.23205,0.1339746 z"
sodipodi:nodetypes="ccccc" />
<g
aria-label="G"
id="text846"
style="font-style:normal;font-weight:normal;font-size:12px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none">
<path
d="m 13,2 c 0,0 -3,0 -3,3 0,3 0,5 3,5 2,0 3,0 3,-2 V 6 h -3 v 1 h 2 v 1 c 0,1 -1,1 -2,1 -1,0 -2,0 -2,-4 0,-1 1,-2 2,-2 2,0 2,1 2,1 h 1 c 0,0 0,-2 -3,-2 z"
style="font-size:12px"
id="path848"
sodipodi:nodetypes="sssccccccsssccs" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24.0px"
height="24.0px"
viewBox="0 0 24.0 24.0"
version="1.1"
id="SVGRoot"
sodipodi:docname="Somme gains Bode.svg"
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
<defs
id="defs1469" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.4"
inkscape:cx="22.985246"
inkscape:cy="9.8906279"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="true"
inkscape:window-width="1920"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid2039" />
<inkscape:grid
type="xygrid"
id="grid2058" />
</sodipodi:namedview>
<metadata
id="metadata1472">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1">
<path
id="path1414"
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0"
d="m 19.979376,19.120606 a 3.5,3.5 0 0 1 -3.06333,3.854578 3.5,3.5 0 0 1 -3.886652,-3.022533 3.5,3.5 0 0 1 2.9814,-3.918293 3.5,3.5 0 0 1 3.9495,2.939936" />
<rect
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:16.166;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0"
id="rect835"
width="18"
height="2"
x="0"
y="18.5" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:16px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
x="-0.5703125"
y="11.875"
id="text839"><tspan
sodipodi:role="line"
id="tspan837"
x="-0.5703125"
y="11.875"
style="font-size:16px">ΣG</tspan></text>
<rect
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:19.0663;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0"
id="rect835-3"
width="25.038315"
height="2"
x="-10.17229"
y="23.748709"
ry="0"
transform="rotate(-60)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24.0px"
height="24.0px"
viewBox="0 0 24.0 24.0"
version="1.1"
id="SVGRoot"
sodipodi:docname="Somme phases Bode.svg"
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
<defs
id="defs10" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.4"
inkscape:cx="15.347905"
inkscape:cy="8.3727678"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="true">
<inkscape:grid
type="xygrid"
id="grid19" />
</sodipodi:namedview>
<metadata
id="metadata13">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:#000000;stroke-width:1.41421"
id="rect26"
width="12"
height="2"
x="9"
y="18" />
<rect
style="fill:#000000;stroke-width:0.912867"
id="rect26-3"
width="5"
height="2"
x="19"
y="2" />
<rect
style="fill:#000000;stroke-width:1.5"
id="rect43"
width="2"
height="16"
x="19"
y="4" />
<circle
style="fill:#000000;stroke-width:2.09999"
id="path45"
cx="20"
cy="19"
r="3.5" />
<text
xml:space="preserve"
style="font-size:16px;line-height:1.25;font-family:sans-serif;text-align:center;letter-spacing:0px;text-anchor:middle"
x="8.7617188"
y="11.664062"
id="text49"><tspan
sodipodi:role="line"
id="tspan47"
x="8.7617188"
y="11.664062"
style="font-size:16px">Σφ</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -1,19 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg <svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24.0px" width="24.0px"
height="24.0px" height="24.0px"
viewBox="0 0 24.0 24.0" viewBox="0 0 24.0 24.0"
version="1.1" version="1.1"
id="SVGRoot" id="SVGRoot"
sodipodi:docname="Text.svg" sodipodi:docname="Text.svg"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)" inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs <defs
id="defs1469" /> id="defs1469" />
<sodipodi:namedview <sodipodi:namedview
@ -24,38 +24,23 @@
inkscape:pageopacity="0.0" inkscape:pageopacity="0.0"
inkscape:pageshadow="2" inkscape:pageshadow="2"
inkscape:zoom="22.4" inkscape:zoom="22.4"
inkscape:cx="13.772321" inkscape:cx="13.763421"
inkscape:cy="8.4598214" inkscape:cy="16.975675"
inkscape:document-units="px" inkscape:document-units="px"
inkscape:current-layer="layer1" inkscape:current-layer="layer1"
inkscape:document-rotation="0" inkscape:document-rotation="0"
showgrid="true" showgrid="true"
inkscape:window-width="1920" inkscape:window-width="1920"
inkscape:window-height="1010" inkscape:window-height="1011"
inkscape:window-x="0" inkscape:window-x="0"
inkscape:window-y="0" inkscape:window-y="0"
inkscape:window-maximized="1" inkscape:window-maximized="1">
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1">
<inkscape:grid <inkscape:grid
type="xygrid" type="xygrid"
id="grid2039" id="grid2039" />
originx="0"
originy="0"
spacingy="1"
spacingx="1"
units="px"
visible="true" />
<inkscape:grid <inkscape:grid
type="xygrid" type="xygrid"
id="grid2058" id="grid2058" />
originx="0"
originy="0"
spacingy="1"
spacingx="1"
units="px"
visible="true" />
</sodipodi:namedview> </sodipodi:namedview>
<metadata <metadata
id="metadata1472"> id="metadata1472">
@ -65,37 +50,8 @@
<dc:format>image/svg+xml</dc:format> <dc:format>image/svg+xml</dc:format>
<dc:type <dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:date>2021-2023</dc:date> <dc:title />
<dc:creator>
<cc:Agent>
<dc:title>Ad5001</dc:title>
</cc:Agent>
</dc:creator>
<dc:rights>
<cc:Agent>
<dc:title>(C) Ad5001 2021-2023 - Licensed under CC4.0-BY-NC-SA</dc:title>
</cc:Agent>
</dc:rights>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-nc-sa/4.0/" />
</cc:Work> </cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-nc-sa/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:prohibits
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF> </rdf:RDF>
</metadata> </metadata>
<g <g
@ -112,16 +68,10 @@
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.670999;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.670999;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 4,3 h 8 V 5 H 9 v 14 h 3 v 2 H 4 V 19 H 7 V 5 H 4 Z" d="m 4,3 h 8 V 5 H 9 v 14 h 3 v 2 H 4 V 19 H 7 V 5 H 4 Z"
sodipodi:nodetypes="ccccccccccccc" /> sodipodi:nodetypes="ccccccccccccc" />
<text <path
xml:space="preserve" id="rect837"
style="font-style:normal;font-weight:normal;font-size:17px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none" style="fill:#000000;fill-rule:evenodd;stroke-width:2"
x="13.844" d="m 16,5 h 2 v 3 h 2 v 2 h -2 v 5 c 0,2 2,2 2,2 v 2 c 0,0 -4,0 -4,-4 V 10 H 14 V 8 h 2 z"
y="17.387978" sodipodi:nodetypes="ccccccccccccccc" />
id="text1"><tspan
sodipodi:role="line"
id="tspan1"
x="13.844"
y="17.387978"
style="font-size:17px">t</tspan></text>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -1,17 +1,47 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg <svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24.0px" width="24.0px"
height="24.0px" height="24.0px"
viewBox="0 0 24.0 24.0" viewBox="0 0 24.0 24.0"
version="1.1" version="1.1"
id="SVGRoot" id="SVGRoot"
xmlns="http://www.w3.org/2000/svg" sodipodi:docname="X Cursor.svg"
xmlns:svg="http://www.w3.org/2000/svg" inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs <defs
id="defs1469" /> id="defs1469" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.4"
inkscape:cx="19.545462"
inkscape:cy="13.163586"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="true"
inkscape:window-width="1829"
inkscape:window-height="916"
inkscape:window-x="19"
inkscape:window-y="31"
inkscape:window-maximized="0">
<inkscape:grid
type="xygrid"
id="grid2039" />
<inkscape:grid
type="xygrid"
id="grid2058" />
</sodipodi:namedview>
<metadata <metadata
id="metadata1472"> id="metadata1472">
<rdf:RDF> <rdf:RDF>
@ -20,40 +50,13 @@
<dc:format>image/svg+xml</dc:format> <dc:format>image/svg+xml</dc:format>
<dc:type <dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:date>2021-2023</dc:date> <dc:title />
<dc:creator>
<cc:Agent>
<dc:title>Ad5001</dc:title>
</cc:Agent>
</dc:creator>
<dc:rights>
<cc:Agent>
<dc:title>(C) Ad5001 2021-2023 - Licensed under CC4.0-BY-NC-SA</dc:title>
</cc:Agent>
</dc:rights>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-nc-sa/4.0/" />
</cc:Work> </cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-nc-sa/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:prohibits
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF> </rdf:RDF>
</metadata> </metadata>
<g <g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1"> id="layer1">
<g <g
aria-label="X" aria-label="X"
@ -65,18 +68,18 @@
id="rect12" id="rect12"
width="2" width="2"
height="24" height="24"
x="5" x="17"
y="0" y="0"
ry="2.14841e-13" /> ry="2.14841e-13" />
<text <path
xml:space="preserve" id="rect835"
style="font-style:normal;font-weight:normal;font-size:17px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none" style="fill:#000000;fill-rule:evenodd;stroke-width:2"
x="10.915" d="m 5,2 h 2 l 8,14 h -2 z"
y="13.713" sodipodi:nodetypes="ccccc" />
id="text1"><tspan <path
id="tspan1" id="rect835-6"
x="10.915" style="fill:#000000;fill-rule:evenodd;stroke-width:2"
y="13.713" d="M 15,2 H 13 L 5,16 h 2 z"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:17px;font-family:sans-serif;-inkscape-font-specification:sans-serif">X</tspan></text> sodipodi:nodetypes="ccccc" />
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

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

View file

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

View file

@ -1,10 +0,0 @@
// Loading modules in order
.import "objects.mjs" as Objects
.import "lib/expr-eval/integration.js" as ExprParser
.import "objs/autoload.mjs" as Autoload
.import "math/latex.mjs" as Latex
.import "history/common.mjs" as HistoryCommon
.import "canvas.mjs" as CanvasAPI
.import "io.mjs" as IOAPI
.import "preferences.mjs" as PreferencesAPI

View file

@ -1,522 +0,0 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Module} from "./modules.mjs"
import {textsup} from "./utils.mjs"
import {Expression} from "./mathlib.mjs"
class CanvasAPI extends Module {
constructor() {
super('Canvas', [
Modules.Objects,
Modules.History
])
/** @type {HTMLCanvasElement} */
this._canvas = null
/** @type {CanvasRenderingContext2D} */
this._ctx = null
/**
* @type {Object}
* @property {function(string, string, string)} showDialog
* @private
*/
this._drawingErrorDialog = null
/**
*
* @type {Object.<string, {expression: Expression, value: number, maxDraw: number}>}
*/
this.axesSteps = {
x: {
expression: null,
value: -1,
maxDraw: -1
},
y: {
expression: null,
value: -1,
maxDraw: -1
}
}
}
initialize(canvasObject, drawingErrorDialog) {
this._canvas = canvasObject
this._drawingErrorDialog = drawingErrorDialog
}
get width() { return this._canvas.width }
get height() { return this._canvas.height }
/**
* Minimum x of the diagram, provided from settings.
* @returns {number}
*/
get xmin() { return this._canvas.xmin }
/**
* Zoom on the x-axis of the diagram, provided from settings.
* @returns {number}
*/
get xzoom() { return this._canvas.xzoom }
/**
* Maximum y of the diagram, provided from settings.
* @returns {number}
*/
get ymax() { return this._canvas.ymax }
/**
* Zoom on the y-axis of the diagram, provided from settings.
* @returns {number}
*/
get yzoom() { return this._canvas.yzoom }
/**
* Label used on the x-axis, provided from settings.
* @returns {string}
*/
get xlabel() { return this._canvas.xlabel }
/**
* Label used on the y-axis, provided from settings.
* @returns {string}
*/
get ylabel() { return this._canvas.ylabel }
/**
* Width of lines that will be drawn into the canvas, provided from settings.
* @returns {number}
*/
get linewidth() { return this._canvas.linewidth }
/**
* Font size of the text that will be drawn into the canvas, provided from settings.
* @returns {number}
*/
get textsize() { return this._canvas.textsize }
/**
* True if the canvas should be in logarithmic mode, false otherwise.
* @returns {boolean}
*/
get logscalex() { return this._canvas.logscalex }
/**
* True if the x graduation should be shown, false otherwise.
* @returns {boolean}
*/
get showxgrad() { return this._canvas.showxgrad }
/**
* True if the y graduation should be shown, false otherwise.
* @returns {boolean}
*/
get showygrad() { return this._canvas.showygrad }
/**
* Max power of the logarithmic scaled on the x axis in logarithmic mode.
* @returns {number}
*/
get maxgradx() { return this._canvas.maxgradx }
//
// Methods to draw the canvas
//
requestPaint() {
this._canvas.requestPaint()
}
/**
* Redraws the entire canvas
*/
redraw() {
this._ctx = this._canvas.getContext("2d")
this._computeAxes()
this._reset()
this._drawGrid()
this._drawAxes()
this._drawLabels()
this._ctx.lineWidth = this.linewidth
for(let objType in Modules.Objects.currentObjects) {
for(let obj of Modules.Objects.currentObjects[objType]){
this._ctx.strokeStyle = obj.color
this._ctx.fillStyle = obj.color
if(obj.visible)
try {
obj.draw(this)
} catch(e) {
// Drawing throws an error. Generally, it's due to a new modification (or the opening of a file)
this._drawingErrorDialog.showDialog(objType, obj.name, e.message)
Modules.History.undo()
}
}
}
this._ctx.lineWidth = 1
}
/**
* Calculates informations for drawing gradations for axes.
* @private
*/
_computeAxes() {
let exprY = new Expression(`x*(${this._canvas.yaxisstep})`)
let y1 = exprY.execute(1)
let exprX = new Expression(`x*(${this._canvas.xaxisstep})`)
let x1 = exprX.execute(1)
this.axesSteps = {
x: {
expression: exprX,
value: x1,
maxDraw: Math.ceil(Math.max(Math.abs(this.xmin), Math.abs(this.px2x(this.width)))/x1)
},
y: {
expression: exprY,
value: y1,
maxDraw: Math.ceil(Math.max(Math.abs(this.ymax), Math.abs(this.px2y(this.height)))/y1)
}
}
}
/**
* Resets the canvas to a blank one with default setting.
* @private
*/
_reset(){
// Reset
this._ctx.fillStyle = "#FFFFFF"
this._ctx.strokeStyle = "#000000"
this._ctx.font = `${this.textsize}px sans-serif`
this._ctx.fillRect(0,0,this.width,this.height)
}
/**
* Draws the grid.
* @private
*/
_drawGrid() {
this._ctx.strokeStyle = "#C0C0C0"
if(this.logscalex) {
for(let xpow = -this.maxgradx; xpow <= this.maxgradx; xpow++) {
for(let xmulti = 1; xmulti < 10; xmulti++) {
this.drawXLine(Math.pow(10, xpow)*xmulti)
}
}
} else {
for(let x = 0; x < this.axesSteps.x.maxDraw; x+=1) {
this.drawXLine(x*this.axesSteps.x.value)
this.drawXLine(-x*this.axesSteps.x.value)
}
}
for(let y = 0; y < this.axesSteps.y.maxDraw; y+=1) {
this.drawYLine(y*this.axesSteps.y.value)
this.drawYLine(-y*this.axesSteps.y.value)
}
}
/**
* Draws the graph axes.
* @private
*/
_drawAxes() {
this._ctx.strokeStyle = "#000000"
let axisypos = this.logscalex ? 1 : 0
this.drawXLine(axisypos)
this.drawYLine(0)
let axisypx = this.x2px(axisypos) // X coordinate of Y axis
let axisxpx = this.y2px(0) // Y coordinate of X axis
// Drawing arrows
this.drawLine(axisypx, 0, axisypx-10, 10)
this.drawLine(axisypx, 0, axisypx+10, 10)
this.drawLine(this.width, axisxpx, this.width-10, axisxpx-10)
this.drawLine(this.width, axisxpx, this.width-10, axisxpx+10)
}
/**
* Resets the canvas to a blank one with default setting.
* @private
*/
_drawLabels() {
let axisypx = this.x2px(this.logscalex ? 1 : 0) // X coordinate of Y axis
let axisxpx = this.y2px(0) // Y coordinate of X axis
// Labels
this._ctx.fillStyle = "#000000"
this._ctx.font = `${this.textsize}px sans-serif`
this._ctx.fillText(this.ylabel, axisypx+10, 24)
let textWidth = this._ctx.measureText(this.xlabel).width
this._ctx.fillText(this.xlabel, this.width-14-textWidth, axisxpx-5)
// Axis graduation labels
this._ctx.font = `${this.textsize-4}px sans-serif`
let txtMinus = this._ctx.measureText('-').width
if(this.showxgrad) {
if(this.logscalex) {
for(let xpow = -this.maxgradx; xpow <= this.maxgradx; xpow+=1) {
textWidth = this._ctx.measureText("10"+textsup(xpow)).width
if(xpow !== 0)
this.drawVisibleText("10"+textsup(xpow), this.x2px(Math.pow(10,xpow))-textWidth/2, axisxpx+16+(6*(xpow===1)))
}
} else {
for(let x = 1; x < this.axesSteps.x.maxDraw; x += 1) {
let drawX = x*this.axesSteps.x.value
let txtX = this.axesSteps.x.expression.simplify(x).replace(/^\((.+)\)$/, '$1')
let textHeight = this.measureText(txtX).height
this.drawVisibleText(txtX, this.x2px(drawX)-4, axisxpx+this.textsize/2+textHeight)
this.drawVisibleText('-'+txtX, this.x2px(-drawX)-4, axisxpx+this.textsize/2+textHeight)
}
}
}
if(this.showygrad) {
for(let y = 0; y < this.axesSteps.y.maxDraw; y += 1) {
let drawY = y*this.axesSteps.y.value
let txtY = this.axesSteps.y.expression.simplify(y).replace(/^\((.+)\)$/, '$1')
textWidth = this._ctx.measureText(txtY).width
this.drawVisibleText(txtY, axisypx-6-textWidth, this.y2px(drawY)+4+(10*(y===0)))
if(y !== 0)
this.drawVisibleText('-'+txtY, axisypx-6-textWidth-txtMinus, this.y2px(-drawY)+4)
}
}
this._ctx.fillStyle = "#FFFFFF"
}
//
// Public functions
//
/**
* Draws an horizontal line at x plot coordinate.
* @param {number} x
*/
drawXLine(x) {
if(this.isVisible(x, this.ymax)) {
this.drawLine(this.x2px(x), 0, this.x2px(x), this.height)
}
}
/**
* Draws an vertical line at y plot coordinate
* @param {number} y
* @private
*/
drawYLine(y) {
if(this.isVisible(this.xmin, y)) {
this.drawLine(0, this.y2px(y), this.width, this.y2px(y))
}
}
/**
* Writes multiline text onto the canvas.
* NOTE: The x and y properties here are relative to the canvas, not the plot.
* @param {string} text
* @param {number} x
* @param {number} y
*/
drawVisibleText(text, x, y) {
if(x > 0 && x < this.width && y > 0 && y < this.height) {
text.toString().split("\n").forEach((txt, i) => {
this._ctx.fillText(txt, x, y+(this.textsize*i))
})
}
}
/**
* Draws an image onto the canvas.
* NOTE: The x, y width and height properties here are relative to the canvas, not the plot.
* @param {CanvasImageSource} image
* @param {number} x
* @param {number} y
* @param {number} width
* @param {number} height
*/
drawVisibleImage(image, x, y, width, height) {
this._canvas.markDirty(Qt.rect(x, y, width, height));
this._ctx.drawImage(image, x, y, width, height)
}
/**
* Measures the width and height of a multiline text that would be drawn onto the canvas.
* @param {string} text
* @returns {{width: number, height: number}}
*/
measureText(text) {
let theight = 0
let twidth = 0
let defaultHeight = this.textsize * 1.2 // Approximate but good enough!
for(let txt of text.split("\n")) {
theight += defaultHeight
if(this._ctx.measureText(txt).width > twidth) twidth = this._ctx.measureText(txt).width
}
return {'width': twidth, 'height': theight}
}
/**
* Converts an x coordinate to its relative position on the canvas.
* It supports both logarithmic and non-logarithmic scale depending on the currently selected mode.
* @param {number} x
* @returns {number}
*/
x2px(x) {
return this._canvas.x2px(x)
}
/**
* Converts an y coordinate to it's relative position on the canvas.
* The y-axis not supporting logarithmic scale, it only supports linear conversion.
* @param {number} y
* @returns {number}
*/
y2px(y) {
return this._canvas.y2px(y)
}
/**
* Converts an x 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.
* @param {number} px
* @returns {number}
*/
px2x(px) {
return this._canvas.px2x(px)
}
/**
* Converts an x 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.
* @param {number} px
* @returns {number}
*/
px2y(px) {
return this._canvas.px2y(px)
}
/**
* Checks whether a plot point (x, y) is visible or not on the canvas.
* @param {number} x
* @param {number} y
* @returns {boolean}
*/
isVisible(x, y) {
return (this.x2px(x) >= 0 && this.x2px(x) <= this.width) && (this.y2px(y) >= 0 && this.y2px(y) <= this.height)
}
/**
* Draws a line from plot point (x1, y1) to plot point (x2, y2).
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
*/
drawLine(x1, y1, x2, y2) {
this._ctx.beginPath();
this._ctx.moveTo(x1, y1);
this._ctx.lineTo(x2, y2);
this._ctx.stroke();
}
/**
* Draws a dashed line from plot point (x1, y1) to plot point (x2, y2).
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
* @param {number} dashPxSize
*/
drawDashedLine(x1, y1, x2, y2, dashPxSize = 6) {
this._ctx.setLineDash([dashPxSize/2, dashPxSize]);
this.drawLine(x1, y1, x2, y2)
this._ctx.setLineDash([]);
}
/**
* Renders latex markup ltxText to an image and loads it. Returns a dictionary with three values: source, width and height.
* @param {string} ltxText
* @param {string} color
* @param {function({width: number, height: number, source: string})} callback
*/
renderLatexImage(ltxText, color, callback) {
let [ltxSrc, ltxWidth, ltxHeight] = Latex.render(ltxText, this.textsize, color).split(",")
let imgData = {
"source": ltxSrc,
"width": parseFloat(ltxWidth),
"height": parseFloat(ltxHeight)
};
if(!this._canvas.isImageLoaded(ltxSrc) && !this._canvas.isImageLoading(ltxSrc)){
// Wait until the image is loaded to callback.
this._canvas.loadImage(ltxSrc)
this._canvas.imageLoaders[ltxSrc] = [callback, imgData]
} else {
// Callback directly
callback(imgData)
}
}
//
// Context methods
//
get font() { return this._ctx.font }
set font(value) { return this._ctx.font = value }
/**
* Draws an act on the canvas centered on a point.
* @param {number} x
* @param {number} y
* @param {number} radius
* @param {number} startAngle
* @param {number} endAngle
* @param {boolean} counterclockwise
*/
arc(x, y, radius, startAngle, endAngle, counterclockwise=false) {
this._ctx.beginPath()
this._ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise)
this._ctx.stroke()
}
/**
* Draws a filled circle centered on a point.
* @param {number} x
* @param {number} y
* @param {number} radius
*/
disc(x, y, radius) {
this._ctx.beginPath();
this._ctx.arc(x, y, radius, 0, 2 * Math.PI)
this._ctx.fill();
}
/**
* Draws a filled rectangle onto the canvas.
* @param {number} x
* @param {number} y
* @param {number} w
* @param {number} h
*/
fillRect(x, y, w, h) {
this._ctx.fillRect(x, y, w, h)
}
}
/** @type {CanvasAPI} */
Modules.Canvas = Modules.Canvas || new CanvasAPI()
export const API = Modules.Canvas

View file

@ -26,8 +26,7 @@ var ADDITIONAL_VARCHARS = [
"ₕ","ₖ","ₗ","ₘ","ₙ","ₚ","ₛ", "ₕ","ₖ","ₗ","ₘ","ₙ","ₚ","ₛ",
"ₜ","¹","²","³","⁴","⁵","⁶", "ₜ","¹","²","³","⁴","⁵","⁶",
"⁷","⁸","⁹","⁰","₁","₂","₃", "⁷","⁸","⁹","⁰","₁","₂","₃",
"₄","₅","₆","₇","₈","₉","₀", "₄","₅","₆","₇","₈","₉","₀"
"∞","π"
] ]
function Instruction(type, value) { function Instruction(type, value) {
@ -213,9 +212,7 @@ function evaluate(tokens, expr, values) {
} }
} else if (type === IVAR) { } else if (type === IVAR) {
// Check for variable value // Check for variable value
if (/^__proto__|prototype|constructor$/.test(item.value)) { if (item.value in expr.functions) {
throw new Error('WARNING: Prototype access detected and denied. If you downloaded this file from the internet, this file might be a virus.');
} else if (item.value in expr.functions) {
nstack.push(expr.functions[item.value]); nstack.push(expr.functions[item.value]);
} else if (item.value in expr.unaryOps && expr.parser.isOperatorEnabled(item.value)) { } else if (item.value in expr.unaryOps && expr.parser.isOperatorEnabled(item.value)) {
nstack.push(expr.unaryOps[item.value]); nstack.push(expr.unaryOps[item.value]);
@ -242,10 +239,7 @@ function evaluate(tokens, expr, values) {
nstack.push(f.apply(undefined, args)); nstack.push(f.apply(undefined, args));
} else if(f.execute) { } else if(f.execute) {
// Objects & expressions execution // Objects & expressions execution
if(args.length >= 1) nstack.push(f.execute.apply(f, args));
nstack.push(f.execute.apply(f, args));
else
throw new Error(qsTranslate('error', 'In order to be executed, object %1 must have at least one argument.').arg(f))
} else { } else {
throw new Error(qsTranslate('error', '%1 cannot be executed.').arg(f)); throw new Error(qsTranslate('error', '%1 cannot be executed.').arg(f));
} }
@ -521,7 +515,7 @@ Expression.prototype.substitute = function (variable, expr) {
}; };
Expression.prototype.evaluate = function (values) { Expression.prototype.evaluate = function (values) {
values = Object.assign({}, values, this.parser.consts) values = values || {};
return evaluate(this.tokens, this, values); return evaluate(this.tokens, this, values);
}; };
@ -541,9 +535,8 @@ Expression.prototype.variables = function (options) {
var vars = []; var vars = [];
getSymbols(this.tokens, vars, options); getSymbols(this.tokens, vars, options);
var functions = this.functions; var functions = this.functions;
var consts = this.parser.consts
return vars.filter(function (name) { return vars.filter(function (name) {
return !(name in functions) && !(name in consts); return !(name in functions);
}); });
}; };
@ -581,7 +574,7 @@ function TokenStream(parser, expression) {
this.unaryOps = parser.unaryOps; this.unaryOps = parser.unaryOps;
this.binaryOps = parser.binaryOps; this.binaryOps = parser.binaryOps;
this.ternaryOps = parser.ternaryOps; this.ternaryOps = parser.ternaryOps;
this.builtinConsts = parser.builtinConsts; this.consts = parser.consts;
this.expression = expression; this.expression = expression;
this.savedPosition = 0; this.savedPosition = 0;
this.savedCurrent = null; this.savedCurrent = null;
@ -693,7 +686,7 @@ TokenStream.prototype.isConst = function () {
var i = startPos; var i = startPos;
for (; i < this.expression.length; i++) { for (; i < this.expression.length; i++) {
var c = this.expression.charAt(i); var c = this.expression.charAt(i);
if (c.toUpperCase() === c.toLowerCase() && !ADDITIONAL_VARCHARS.includes(c)) { if (c.toUpperCase() === c.toLowerCase()) {
if (i === this.pos || (c !== '_' && c !== '.' && (c < '0' || c > '9'))) { if (i === this.pos || (c !== '_' && c !== '.' && (c < '0' || c > '9'))) {
break; break;
} }
@ -701,8 +694,8 @@ TokenStream.prototype.isConst = function () {
} }
if (i > startPos) { if (i > startPos) {
var str = this.expression.substring(startPos, i); var str = this.expression.substring(startPos, i);
if (str in this.builtinConsts) { if (str in this.consts) {
this.current = this.newToken(TNUMBER, this.builtinConsts[str]); this.current = this.newToken(TNUMBER, this.consts[str]);
this.pos += str.length; this.pos += str.length;
return true; return true;
} }
@ -1117,7 +1110,7 @@ ParserState.prototype.parseExpression = function (instr) {
if (this.parseUntilEndStatement(instr, exprInstr)) { if (this.parseUntilEndStatement(instr, exprInstr)) {
return; return;
} }
this.parseConditionalExpression(exprInstr); this.parseVariableAssignmentExpression(exprInstr);
if (this.parseUntilEndStatement(instr, exprInstr)) { if (this.parseUntilEndStatement(instr, exprInstr)) {
return; return;
} }
@ -1157,6 +1150,37 @@ ParserState.prototype.parseArrayList = function (instr) {
return argCount; return argCount;
}; };
ParserState.prototype.parseVariableAssignmentExpression = function (instr) {
this.parseConditionalExpression(instr);
while (this.accept(TOP, '=')) {
var varName = instr.pop();
var varValue = [];
var lastInstrIndex = instr.length - 1;
if (varName.type === IFUNCALL) {
if (!this.tokens.isOperatorEnabled('()=')) {
throw new Error(qsTranslate('error', 'Function definition is not permitted.'));
}
for (var i = 0, len = varName.value + 1; i < len; i++) {
var index = lastInstrIndex - i;
if (instr[index].type === IVAR) {
instr[index] = new Instruction(IVARNAME, instr[index].value);
}
}
this.parseVariableAssignmentExpression(varValue);
instr.push(new Instruction(IEXPR, varValue));
instr.push(new Instruction(IFUNDEF, varName.value));
continue;
}
if (varName.type !== IVAR && varName.type !== IMEMBER) {
throw new Error(qsTranslate('error', 'Expected variable for assignment.'));
}
this.parseVariableAssignmentExpression(varValue);
instr.push(new Instruction(IVARNAME, varName.value));
instr.push(new Instruction(IEXPR, varValue));
instr.push(binaryInstruction('='));
}
};
ParserState.prototype.parseConditionalExpression = function (instr) { ParserState.prototype.parseConditionalExpression = function (instr) {
this.parseOrExpression(instr); this.parseOrExpression(instr);
while (this.accept(TOP, '?')) { while (this.accept(TOP, '?')) {
@ -1588,29 +1612,25 @@ function arrayIndex(array, index) {
function max(array) { function max(array) {
if (arguments.length === 1 && Array.isArray(array)) { if (arguments.length === 1 && Array.isArray(array)) {
return Math.max.apply(Math, array); return Math.max.apply(Math, array);
} else if(arguments.length >= 1) {
return Math.max.apply(Math, arguments);
} else { } else {
throw new EvalError(qsTranslate('error', 'Function %1 must have at least one argument.').arg('max')) return Math.max.apply(Math, arguments);
} }
} }
function min(array) { function min(array) {
if (arguments.length === 1 && Array.isArray(array)) { if (arguments.length === 1 && Array.isArray(array)) {
return Math.min.apply(Math, array); return Math.min.apply(Math, array);
} else if(arguments.length >= 1) {
return Math.min.apply(Math, arguments);
} else { } else {
throw new EvalError(qsTranslate('error', 'Function %1 must have at least one argument.').arg('min')) return Math.min.apply(Math, arguments);
} }
} }
function arrayMap(f, a) { function arrayMap(f, a) {
if (typeof f !== 'function') { if (typeof f !== 'function') {
throw new EvalError(qsTranslate('error', 'First argument to map is not a function.')); throw new Error(qsTranslate('error', 'First argument to map is not a function.'));
} }
if (!Array.isArray(a)) { if (!Array.isArray(a)) {
throw new EvalError(qsTranslate('error', 'Second argument to map is not an array.')); throw new Error(qsTranslate('error', 'Second argument to map is not an array.'));
} }
return a.map(function (x, i) { return a.map(function (x, i) {
return f(x, i); return f(x, i);
@ -1619,10 +1639,10 @@ function arrayMap(f, a) {
function arrayFold(f, init, a) { function arrayFold(f, init, a) {
if (typeof f !== 'function') { if (typeof f !== 'function') {
throw new EvalError(qsTranslate('error', 'First argument to fold is not a function.')); throw new Error(qsTranslate('error', 'First argument to fold is not a function.'));
} }
if (!Array.isArray(a)) { if (!Array.isArray(a)) {
throw new EvalError(qsTranslate('error', 'Second argument to fold is not an array.')); throw new Error(qsTranslate('error', 'Second argument to fold is not an array.'));
} }
return a.reduce(function (acc, x, i) { return a.reduce(function (acc, x, i) {
return f(acc, x, i); return f(acc, x, i);
@ -1631,10 +1651,10 @@ function arrayFold(f, init, a) {
function arrayFilter(f, a) { function arrayFilter(f, a) {
if (typeof f !== 'function') { if (typeof f !== 'function') {
throw new EvalError(qsTranslate('error', 'First argument to filter is not a function.')); throw new Error(qsTranslate('error', 'First argument to filter is not a function.'));
} }
if (!Array.isArray(a)) { if (!Array.isArray(a)) {
throw new EvalError(qsTranslate('error', 'Second argument to filter is not an array.')); throw new Error(qsTranslate('error', 'Second argument to filter is not an array.'));
} }
return a.filter(function (x, i) { return a.filter(function (x, i) {
return f(x, i); return f(x, i);
@ -1753,7 +1773,6 @@ class Parser {
atan2: Math.atan2, atan2: Math.atan2,
'if': condition, 'if': condition,
gamma: gamma, gamma: gamma,
'Γ': gamma,
roundTo: roundTo, roundTo: roundTo,
map: arrayMap, map: arrayMap,
fold: arrayFold, fold: arrayFold,
@ -1762,12 +1781,12 @@ class Parser {
join: arrayJoin join: arrayJoin
}; };
// These constants will automatically be replaced the MOMENT they are parsed. this.consts = {
// (Original consts from the parser) E: Math.E,
this.builtinConsts = {}; PI: Math.PI,
// These consts will only be replaced when the expression is evaluated. 'true': true,
this.consts = {} 'false': false
};
} }
parse(expr) { parse(expr) {

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,10 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import EditedProperty from "editproperty.mjs" .pragma library
import Objects from "../objects.mjs"
export default class ColorChanged extends EditedProperty { .import "editproperty.js" as EP
.import "../objects.js" as Objects
class ColorChanged extends EP.EditedProperty {
// Action used everytime when an object's color is changed // Action used everytime when an object's color is changed
type(){return 'ColorChanged'} type(){return 'ColorChanged'}

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,36 +16,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Module } from "../modules.mjs" .pragma library
import Latex from "../math/latex.mjs"
.import "../math/latex.js" as Latex
var themeTextColor;
var imageDepth = 2;
var fontSize = 14;
class HistoryCommonAPI extends Module { class Action {
constructor() {
super('History', [
Modules.Latex
])
// History QML object
this.history = null;
this.themeTextColor = "#ff0000";
this.imageDepth = 2;
this.fontSize = 14;
}
undo() { this.history.undo() }
redo() { this.history.redo() }
clear() { this.history.clear() }
addToHistory(action) { this.history.addToHistory(action) }
unserialize(...data) { this.history.unserialize(...data) }
serialize() { return this.history.serialize() }
}
/** @type {HistoryCommonAPI} */
Modules.History = Modules.History || new HistoryCommonAPI()
export const API = Modules.History
export class Action {
/** /**
* Type of the action. * Type of the action.
* *
@ -68,11 +48,15 @@ export class Action {
/** /**
* Undoes the action. * Undoes the action.
*
* @returns {string}
*/ */
undo() {} undo() {}
/** /**
* Redoes the action. * Redoes the action.
*
* @returns {string}
*/ */
redo() {} redo() {}
@ -80,7 +64,7 @@ export class Action {
* Export the action to a serializable format. * Export the action to a serializable format.
* NOTE: These arguments will be reinputed in the constructor in this order. * NOTE: These arguments will be reinputed in the constructor in this order.
* *
* @returns {string[]} * @returns {string}
*/ */
export() { export() {
return [this.targetName, this.targetType] return [this.targetName, this.targetType]
@ -102,7 +86,7 @@ export class Action {
* @returns {string} * @returns {string}
*/ */
getIconRichText(type) { getIconRichText(type) {
return `<img source="../icons/objects/${type}.svg" style="color: ${Modules.History.themeTextColor};" width=18 height=18></img>` return `<img source="../icons/objects/${type}.svg" style="color: ${themeTextColor};" width=18 height=18></img>`
} }
/** /**
@ -114,17 +98,12 @@ export class Action {
renderLatexAsHtml(latexString) { renderLatexAsHtml(latexString) {
if(!Latex.enabled) if(!Latex.enabled)
throw new Error("Cannot render an item as LaTeX when LaTeX is disabled.") throw new Error("Cannot render an item as LaTeX when LaTeX is disabled.")
let imgDepth = Modules.History.imageDepth let latexInfo = Latex.Renderer.render(latexString, imageDepth*fontSize+4, themeTextColor).split(",")
let [src, width, height] = Latex.render( return `<img src="${latexInfo[0]}" width="${parseInt(latexInfo[1])/imageDepth}" height="${parseInt(latexInfo[2])/imageDepth}" style="vertical-align: middle"></img>`
latexString,
imgDepth * (Modules.History.fontSize + 2),
Modules.History.themeTextColor
).split(",")
return `<img src="${src}" width="${parseInt(width)/imgDepth}" height="${parseInt(height)/imgDepth}" style="vertical-align: middle"/>`
} }
/** /**
* Returns a string with the HTML-formatted description of the action. * Returns a string with the HTML-formated description of the action.
* *
* @returns {string} * @returns {string}
*/ */

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,10 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import Objects from "../objects.mjs" .pragma library
import { Action } from "common.mjs"
export default class CreateNewObject extends Action { .import "../objects.js" as Objects
.import "common.js" as C
class CreateNewObject extends C.Action {
// Action used for the creation of an object // Action used for the creation of an object
type(){return 'CreateNewObject'} type(){return 'CreateNewObject'}
@ -35,6 +37,10 @@ export default class CreateNewObject extends Action {
undo() { undo() {
Objects.deleteObject(this.targetName) Objects.deleteObject(this.targetName)
//let targetIndex = Objects.getObjectsName(this.targetType).indexOf(this.targetName)
//delete Objects.currentObjectsByName[this.targetName]
//Objects.currentObjects[this.targetType][targetIndex].delete()
//Objects.currentObjects[this.targetType].splice(targetIndex, 1)
} }
redo() { redo() {

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,14 +16,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import Objects from "../objects.mjs" .pragma library
import CreateNewObject from "create.mjs"
.import "../objects.js" as Objects
.import "create.js" as Create
export default class DeleteObject extends CreateNewObject { class DeleteObject extends Create.CreateNewObject {
/** // Action used at the deletion of an object. Basicly the same thing as creating a new object, except Redo & Undo are reversed.
* Action used at the deletion of an object. Basically the same thing as creating a new object, except Redo & Undo are reversed.
*/
type(){return 'DeleteObject'} type(){return 'DeleteObject'}
icon(){return 'delete'} icon(){return 'delete'}

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,13 +16,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import Objects from "../objects.mjs" .pragma library
import Latex from "../math/latex.mjs"
import * as MathLib from "../mathlib.mjs"
import { Action } from "common.mjs"
import { DrawableObject } from "../objs/common.mjs"
export default class EditedProperty extends Action { .import "../objects.js" as Objects
.import "../math/latex.js" as Latex
.import "../mathlib.js" as MathLib
.import "../objs/common.js" as Common
.import "common.js" as C
class EditedProperty extends C.Action {
// Action used everytime an object's property has been changed // Action used everytime an object's property has been changed
type(){return 'EditedProperty'} type(){return 'EditedProperty'}
@ -31,16 +33,7 @@ export default class EditedProperty extends Action {
color(darkVer=false){ color(darkVer=false){
return darkVer ? 'darkslateblue' : 'cyan'; return darkVer ? 'darkslateblue' : 'cyan';
} }
/**
*
* @param {string} targetName - Name of the object to target
* @param {string} targetType - Type of the object to target.
* @param {string} targetProperty - Property being changed
* @param {any} previousValue - Previous value before change
* @param {any} newValue - New value after change
* @param {boolean} valueIsExpressionNeedingImport - True if the value needs to be imported. (e.g expressions)
*/
constructor(targetName = "", targetType = "Point", targetProperty = "visible", previousValue = false, newValue = true, valueIsExpressionNeedingImport = false) { constructor(targetName = "", targetType = "Point", targetProperty = "visible", previousValue = false, newValue = true, valueIsExpressionNeedingImport = false) {
super(targetName, targetType) super(targetName, targetType)
this.targetProperty = targetProperty this.targetProperty = targetProperty
@ -49,10 +42,10 @@ export default class EditedProperty extends Action {
this.newValue = newValue this.newValue = newValue
this.propertyType = Objects.types[targetType].properties()[targetProperty] this.propertyType = Objects.types[targetType].properties()[targetProperty]
if(valueIsExpressionNeedingImport) { if(valueIsExpressionNeedingImport) {
if(typeof this.propertyType == 'object' && this.propertyType.type === "Expression") { if(typeof this.propertyType == 'object' && this.propertyType.type == "Expression") {
this.previousValue = new MathLib.Expression(this.previousValue); this.previousValue = new MathLib.Expression(this.previousValue);
this.newValue = new MathLib.Expression(this.newValue); this.newValue = new MathLib.Expression(this.newValue);
} else if(this.propertyType === "Domain") { } else if(this.propertyType == "Domain") {
this.previousValue = MathLib.parseDomain(this.previousValue); this.previousValue = MathLib.parseDomain(this.previousValue);
this.newValue = MathLib.parseDomain(this.newValue); this.newValue = MathLib.parseDomain(this.newValue);
} else { } else {
@ -77,7 +70,7 @@ export default class EditedProperty extends Action {
export() { export() {
if(this.previousValue instanceof MathLib.Expression) { if(this.previousValue instanceof MathLib.Expression) {
return [this.targetName, this.targetType, this.targetProperty, this.previousValue.toEditableString(), this.newValue.toEditableString(), true] return [this.targetName, this.targetType, this.targetProperty, this.previousValue.toEditableString(), this.newValue.toEditableString(), true]
} else if(this.previousValue instanceof DrawableObject) { } else if(this.previousValue instanceof Common.DrawableObject) {
return [this.targetName, this.targetType, this.targetProperty, this.previousValue.name, this.newValue.name, true] return [this.targetName, this.targetType, this.targetProperty, this.previousValue.name, this.newValue.name, true]
} else { } else {
return [this.targetName, this.targetType, this.targetProperty, this.previousValue, this.newValue, false] return [this.targetName, this.targetType, this.targetProperty, this.previousValue, this.newValue, false]
@ -117,7 +110,7 @@ export default class EditedProperty extends Action {
// HTML // HTML
this.prevHTML = '<tt style="background: rgba(128,128,128,0.1);">&nbsp;'+this.prevString+'&nbsp;</tt>' this.prevHTML = '<tt style="background: rgba(128,128,128,0.1);">&nbsp;'+this.prevString+'&nbsp;</tt>'
this.nextHTML = '<tt style="background: rgba(128,128,128,0.1);">&nbsp;'+this.nextString+'&nbsp;</tt>' this.nextHTML = '<tt style="background: rgba(128,128,128,0.1);">&nbsp;'+this.nextString+'&nbsp;</tt>'
if(Latex.enabled && typeof this.propertyType == 'object' && this.propertyType.type === "Expression") { if(Latex.enabled && typeof this.propertyType == 'object' && this.propertyType.type == "Expression") {
this.prevHTML= this.renderLatexAsHtml(this.previousValue.latexMarkup) this.prevHTML= this.renderLatexAsHtml(this.previousValue.latexMarkup)
this.nextHTML= this.renderLatexAsHtml(this.newValue.latexMarkup) this.nextHTML= this.renderLatexAsHtml(this.newValue.latexMarkup)
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,16 +16,19 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import EditedProperty from "editproperty.mjs" .pragma library
import Objects from "../objects.mjs"
.import "editproperty.js" as EP
.import "../objects.js" as Objects
export default class NameChanged extends EditedProperty { class NameChanged extends EP.EditedProperty {
// Action used everytime an object's property has been changed // Action used everytime an object's property has been changed
type(){return 'NameChanged'} type(){return 'NameChanged'}
icon(){return 'name'} icon(){return 'name'}
color(darkVer=false){return darkVer ? 'darkorange' : 'orange'} color(darkVer=false){return darkVer ? 'darkorange' : 'orange'}
constructor(targetName = "", targetType = "Point", newName = "") { constructor(targetName = "", targetType = "Point", newName = "") {
@ -42,6 +45,10 @@ export default class NameChanged extends EditedProperty {
redo() { redo() {
Objects.renameObject(this.previousValue, this.newValue) Objects.renameObject(this.previousValue, this.newValue)
//let obj = Objects.currentObjectsByName[this.previousValue]
//obj.name = this.newValue
//Objects.currentObjectsByName[this.newValue] = obj
//delete Objects.currentObjectsByName[this.previousValue]
} }
getReadableString() { getReadableString() {

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,14 +16,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import Objects from "../objects.mjs" .pragma library
import Latex from "../math/latex.mjs"
import * as MathLib from "../mathlib.mjs"
import { escapeHTML } from "../utils.mjs"
import { Action } from "common.mjs"
import { DrawableObject } from "../objs/common.mjs"
export default class EditedPosition extends Action { .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...) // Action used for objects that have a X and Y expression properties (points, texts...)
type(){return 'EditedPosition'} type(){return 'EditedPosition'}
@ -66,8 +68,8 @@ export default class EditedPosition extends Action {
this.prevHTML = this.renderLatexAsHtml(`\\left(${this.previousXValue.latexMarkup},${this.previousYValue.latexMarkup}\\right)`) this.prevHTML = this.renderLatexAsHtml(`\\left(${this.previousXValue.latexMarkup},${this.previousYValue.latexMarkup}\\right)`)
this.nextHTML = this.renderLatexAsHtml(`\\left(${this.newXValue.latexMarkup},${this.newYValue.latexMarkup}\\right)`) this.nextHTML = this.renderLatexAsHtml(`\\left(${this.newXValue.latexMarkup},${this.newYValue.latexMarkup}\\right)`)
} else { } else {
this.prevHTML = '<tt style="background: rgba(128,128,128,0.1);">&nbsp;'+escapeHTML(this.prevString)+'&nbsp;</tt>' this.prevHTML = '<tt style="background: rgba(128,128,128,0.1);">&nbsp;'+Utils.escapeHTML(this.prevString)+'&nbsp;</tt>'
this.nextHTML = '<tt style="background: rgba(128,128,128,0.1);">&nbsp;'+escapeHTML(this.nextString)+'&nbsp;</tt>' this.nextHTML = '<tt style="background: rgba(128,128,128,0.1);">&nbsp;'+Utils.escapeHTML(this.nextString)+'&nbsp;</tt>'
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,11 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import EditedProperty from "editproperty.mjs" .pragma library
import Objects from "../objects.mjs"
.import "editproperty.js" as EP
.import "../objects.js" as Objects
export default class EditedVisibility extends EditedProperty { class EditedVisibility extends EP.EditedProperty {
// Action used when an object's shown or hidden. // Action used when an object's shown or hidden.
type(){return 'EditedVisibility'} type(){return 'EditedVisibility'}

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -18,27 +18,30 @@
// This library helps containing actions to be undone or redone (in other words, editing history) // This library helps containing actions to be undone or redone (in other words, editing history)
// Each type of event is repertoried as an action that can be listed for everything that's undoable. // Each type of event is repertoried as an action that can be listed for everything that's undoable.
.pragma library
import { Action as A } from "history/common.mjs" .import "history/common.js" as Common
import Create from "history/create.mjs" .import "history/create.js" as Create
import Delete from "history/delete.mjs" .import "history/delete.js" as Delete
import EP from "history/editproperty.mjs" .import "history/editproperty.js" as EP
import Pos from "history/position.mjs" .import "history/position.js" as Pos
import V from "history/visibility.mjs" .import "history/visibility.js" as V
import Name from "history/name.mjs" .import "history/name.js" as Name
import Color from "history/color.mjs" .import "history/color.js" as Color
var history = null;
export const Action = A var Action = Common.Action
export const CreateNewObject = Create var CreateNewObject = Create.CreateNewObject
export const DeleteObject = Delete var DeleteObject = Delete.DeleteObject
export const EditedProperty = EP var EditedProperty = EP.EditedProperty
export const EditedPosition = Pos var EditedPosition = Pos.EditedPosition
export const EditedVisibility = V var EditedVisibility = V.EditedVisibility
export const NameChanged = Name var NameChanged = Name.NameChanged
export const ColorChanged = Color var ColorChanged = Color.ColorChanged
export const Actions = { var Actions = {
"Action": Action, "Action": Action,
"CreateNewObject": CreateNewObject, "CreateNewObject": CreateNewObject,
"DeleteObject": DeleteObject, "DeleteObject": DeleteObject,

View file

@ -1,169 +0,0 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Module} from "./modules.mjs"
class IOAPI extends Module {
constructor() {
super('IO', [
Modules.Objects,
Modules.History
])
/**
* Path of the currently opened file. Empty if no file is opened.
* @type {string}
*/
this.saveFileName = ""
}
/**
* Initializes module with QML elements.
* @param {LogarithmPlotter} rootElement
* @param {Settings} settings
* @param {{show: function(string)}} alert
*/
initialize(rootElement, settings, alert) {
this.rootElement = rootElement
this.settings = settings
this.alert = alert
}
/**
* Saves the diagram to a certain \c filename.
* @param {string} filename
*/
saveDiagram(filename) {
// Add extension if necessary
if(['lpf'].indexOf(filename.split('.')[filename.split('.').length-1]) === -1)
filename += '.lpf'
this.saveFilename = filename
let objs = {}
for(let objType in Modules.Objects.currentObjects){
objs[objType] = []
for(let obj of Modules.Objects.currentObjects[objType]) {
objs[objType].push(obj.export())
}
}
let settings = {
"xzoom": this.settings.xzoom,
"yzoom": this.settings.yzoom,
"xmin": this.settings.xmin,
"ymax": this.settings.ymax,
"xaxisstep": this.settings.xaxisstep,
"yaxisstep": this.settings.yaxisstep,
"xaxislabel": this.settings.xlabel,
"yaxislabel": this.settings.ylabel,
"logscalex": this.settings.logscalex,
"linewidth": this.settings.linewidth,
"showxgrad": this.settings.showxgrad,
"showygrad": this.settings.showygrad,
"textsize": this.settings.textsize,
"history": Modules.History.serialize(),
"width": this.rootElement.width,
"height": this.rootElement.height,
"objects": objs,
"type": "logplotv1"
}
Helper.write(filename, JSON.stringify(settings))
this.alert.show(qsTranslate('io', "Saved plot to '%1'.").arg(filename.split("/").pop()))
Modules.History.history.saved = true
}
/**
* Loads the diagram from a certain \c filename.
* @param {string} filename
*/
loadDiagram(filename) {
let basename = filename.split("/").pop()
this.alert.show(qsTranslate('io', "Loading file '%1'.").arg(basename))
let data = JSON.parse(Helper.load(filename))
let error = "";
if(Object.keys(data).includes("type") && data["type"] === "logplotv1") {
Modules.History.clear()
// Importing settings
this.settings.saveFilename = filename
this.settings.xzoom = parseFloat(data["xzoom"]) || 100
this.settings.yzoom = parseFloat(data["yzoom"]) || 10
this.settings.xmin = parseFloat(data["xmin"]) || 5/10
this.settings.ymax = parseFloat(data["ymax"]) || 24
this.settings.xaxisstep = data["xaxisstep"] || "4"
this.settings.yaxisstep = data["yaxisstep"] || "4"
this.settings.xlabel = data["xaxislabel"] || ""
this.settings.ylabel = data["yaxislabel"] || ""
this.settings.logscalex = data["logscalex"] === true
if("showxgrad" in data)
this.settings.showxgrad = data["showxgrad"]
if("showygrad" in data)
this.settings.textsize = data["showygrad"]
if("linewidth" in data)
this.settings.linewidth = data["linewidth"]
if("textsize" in data)
this.settings.textsize = data["textsize"]
this.rootElement.height = parseFloat(data["height"]) || 500
this.rootElement.width = parseFloat(data["width"]) || 1000
// Importing objects
Modules.Objects.currentObjects = {}
for(let key of Object.keys(Modules.Objects.currentObjectsByName)) {
delete Modules.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(Modules.Objects.types).indexOf(objType) > -1) {
Modules.Objects.currentObjects[objType] = []
for(let objData of data['objects'][objType]) {
/** @type {DrawableObject} */
let obj = Modules.Objects.types[objType].import(...objData)
Modules.Objects.currentObjects[objType].push(obj)
Modules.Objects.currentObjectsByName[obj.name] = obj
}
} else {
error += qsTranslate('io', "Unknown object type: %1.").arg(objType) + "\n";
}
}
// Updating object dependencies.
for(let objName in Modules.Objects.currentObjectsByName)
Modules.Objects.currentObjectsByName[objName].update()
// Importing history
if("history" in data)
Modules.History.unserialize(...data["history"])
// Refreshing sidebar
this.rootElement.updateObjectsLists()
} else {
error = qsTranslate('io', "Invalid file provided.")
}
if(error !== "") {
console.log(error)
this.alert.show(qsTranslate('io', "Could not save file: ") + error)
// TODO: Error handling
return
}
Modules.Canvas.redraw()
this.alert.show(qsTranslate('io', "Loaded file '%1'.").arg(basename))
Modules.History.history.saved = true
}
}
/** @type {IOAPI} */
Modules.IO = Modules.IO || new IOAPI()

View file

@ -1,119 +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 "../../modules.mjs" as M
const 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
}
class ExprParserAPI extends M.Module {
constructor() {
super('ExprParser', [
/** @type {ObjectsAPI} */
Modules.Objects
])
this.currentVars = {}
this.Internals = ExprEval
this._parser = new ExprEval.Parser()
this._parser.consts = Object.assign({}, this._parser.consts, evalVariables)
this._parser.functions.integral = this.integral.bind(this)
this._parser.functions.derivative = this.derivative.bind(this)
}
/**
* 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 {function} JS function to call.
*/
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 = this._parser.parse(f).toJSFunction(variable, this.currentVars)
} else
throw EvalError(qsTranslate('usage', 'Usage: %1 or\n%2').arg(usage1).arg(usage2))
return f
}
/**
* @param {string} expression - Expression to parse
*/
parse(expression) {
return this._parser.parse(expression)
}
integral(a, b, ...args) {
let usage1 = qsTranslate('usage', 'integral(<from: number>, <to: number>, <f: ExecutableObject>)')
let usage2 = qsTranslate('usage', 'integral(<from: number>, <to: number>, <f: string>, <variable: string>)')
let f = this.parseArgumentsForFunction(args, usage1, usage2)
if(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))
}
derivative(...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 = this.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
}
}
/** @type {ExprParserAPI} */
Modules.ExprParser = Modules.ExprParser || new ExprParserAPI()

View file

@ -1,70 +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/>.
*/
// Type polyfills for IDEs.
// Never directly imported.
Modules = Modules || {}
/** @type {function(string, string): string} */
qsTranslate = qsTranslate || function(category, string) { throw new Error('qsTranslate not implemented.'); }
/** @type {function(string): string} */
qsTr = qsTr || function(string) { throw new Error('qsTr not implemented.'); }
/** @type {function(string, string): string} */
QT_TRANSLATE_NOOP = QT_TRANSLATE_NOOP || function(string, string) { throw new Error('QT_TRANSLATE_NOOP not implemented.'); }
/** @type {function(string): string} */
QT_TR_NOOP = QT_TR_NOOP || function(string) { throw new Error('QT_TR_NOOP not implemented.'); }
/** @type {function(string|boolean|int): string} */
String.prototype.arg = String.prototype.arg || function(parameter) { throw new Error('arg not implemented.'); }
const Qt = {
/**
* @param {number} x
* @param {number} y
* @param {number} width
* @param {number} height
* @returns {{x, width, y, height}}
*/
rect: function(x, y, width, height) {
return {x: x, y: y, width: width, height: height};
}
}
/** Typehints for Helper. */
const Helper = {
/** @type {function(string): boolean} */
getSettingBool: (setting) => true,
/** @type {function(string): int} */
getSettingInt: (setting) => 0,
/** @type {function(string): string} */
getSetting: (setting) => '',
/** @type {function(string, boolean)} */
setSettingBool: (setting, value) => {},
/** @type {function(string, int)} */
setSettingInt: (setting, value) => 0,
/** @type {function(string, string)} */
setSetting: (setting, value) => '',
/** @type {function(string, string)} */
write: (filename, data) => {},
/** @type {function(string): string} */
load: (filename) => '',
}
const Latex = {
/** @type {function(string, number, string): string} */
render: (latex_markup, font_size, color) => '',
}

View file

@ -0,0 +1,56 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 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
const DERIVATION_PRECISION = 0.1
var evalVariables = { // Variables not provided by expr-eval.js, needs to be provided manually
"pi": Math.PI,
"π": Math.PI,
"inf": Infinity,
"infinity": Infinity,
"Infinity": Infinity,
"∞": Infinity,
"e": Math.E,
"E": Math.E
}
var currentVars = {}
var currentObjectsByName = {} // Mirror of currentObjectsByName in objects.js
const parser = new ExprEval.Parser()
parser.consts = Object.assign({}, parser.consts, evalVariables)
// Function definition
parser.functions.integral = function(a, b, f, variable) {
// https://en.wikipedia.org/wiki/Simpson%27s_rule
// Simpler, faster than tokenizing the expression
f = parser.parse(f).toJSFunction(variable, currentVars)
return (b-a)/6*(f(a)+4*f((a+b)/2)+f(b))
}
parser.functions.derivative = function(f, variable, x) {
f = parser.parse(f).toJSFunction(variable, currentVars)
return (f(x+DERIVATION_PRECISION/2)-f(x-DERIVATION_PRECISION/2))/DERIVATION_PRECISION
}

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,19 +16,21 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Expression, executeExpression } from "expression.mjs" .pragma library
.import "expression.js" as Expr
/** /**
* Main abstract domain class * Main abstract domain class
* It doesn't represent any kind of domain and is meant to be extended. * It doesn't represent any kind of domain and is meant to be extended.
*/ */
export class Domain { class Domain {
constructor() {} constructor() {}
/** /**
* Checks whether x is included in the domain. * Checks whether x is included in the domain.
* @param {number} x - The x value. * @param {number} x - The x value.
* @return {boolean} true if included, false otherwise. * @return {bool} true if included, false otherwise.
*/ */
includes(x) { return false } includes(x) { return false }
@ -70,13 +72,11 @@ export class Domain {
case "RP": case "RP":
case "R+": case "R+":
case "ℝ⁺": case "ℝ⁺":
case "+":
return Domain.RP return Domain.RP
break; break;
case "RM": case "RM":
case "R-": case "R-":
case "ℝ⁻": case "ℝ⁻":
case "-":
return Domain.RM return Domain.RM
break; break;
case "RPE": case "RPE":
@ -85,8 +85,6 @@ export class Domain {
case "R*+": case "R*+":
case "*⁺": case "*⁺":
case "ℝ⁺*": case "ℝ⁺*":
case "*+":
case "+*":
return Domain.RPE return Domain.RPE
break; break;
case "RME": case "RME":
@ -95,21 +93,16 @@ export class Domain {
case "R*-": case "R*-":
case "ℝ⁻*": case "ℝ⁻*":
case "*⁻": case "*⁻":
case "-*":
case "*-":
return Domain.RME return Domain.RME
break; break;
case "": case "":
case "N": case "N":
case "ZP": case "ZP":
case "Z+":
case "ℤ⁺": case "ℤ⁺":
case "+":
return Domain.N return Domain.N
break; break;
case "NLOG": case "NLOG":
case "ℕˡᵒᵍ": case "ℕˡᵒᵍ":
case "LOG":
return Domain.NLog return Domain.NLog
break; break;
case "NE": case "NE":
@ -118,15 +111,12 @@ export class Domain {
case "N+": case "N+":
case "*": case "*":
case "ℕ⁺": case "ℕ⁺":
case "+":
case "ZPE": case "ZPE":
case "ZEP": case "ZEP":
case "Z+*": case "Z+*":
case "Z*+": case "Z*+":
case "ℤ⁺*": case "ℤ⁺*":
case "*⁺": case "*⁺":
case "+*":
case "*+":
return Domain.NE return Domain.NE
break; break;
case "Z": case "Z":
@ -136,7 +126,6 @@ export class Domain {
case "ZM": case "ZM":
case "Z-": case "Z-":
case "ℤ⁻": case "ℤ⁻":
case "-":
return Domain.ZM return Domain.ZM
break; break;
case "ZME": case "ZME":
@ -145,8 +134,6 @@ export class Domain {
case "Z*-": case "Z*-":
case "ℤ⁻*": case "ℤ⁻*":
case "*⁻": case "*⁻":
case "-*":
case "*-":
return Domain.ZME return Domain.ZME
break; break;
case "ZE": case "ZE":
@ -164,7 +151,7 @@ export class Domain {
/** /**
* Represents an empty set. * Represents an empty set.
*/ */
export class EmptySet extends Domain { class EmptySet extends Domain {
constructor() { constructor() {
super() super()
this.displayName = "∅" this.displayName = "∅"
@ -185,12 +172,12 @@ export class EmptySet extends Domain {
/** /**
* Domain classes for ranges (e.g ]0;3[, [1;2[ ...) * Domain classes for ranges (e.g ]0;3[, [1;2[ ...)
*/ */
export class Range extends Domain { class Range extends Domain {
constructor(begin, end, openBegin, openEnd) { constructor(begin, end, openBegin, openEnd) {
super() super()
if(typeof begin == 'number' || typeof begin == 'string') begin = new Expression(begin.toString()) if(typeof begin == 'number' || typeof begin == 'string') begin = new Expr.Expression(begin.toString())
this.begin = begin this.begin = begin
if(typeof end == 'number' || typeof end == 'string') end = new Expression(end.toString()) if(typeof end == 'number' || typeof end == 'string') end = new Expr.Expression(end.toString())
this.end = end this.end = end
this.openBegin = openBegin this.openBegin = openBegin
this.openEnd = openEnd this.openEnd = openEnd
@ -199,7 +186,7 @@ export class Range extends Domain {
} }
includes(x) { includes(x) {
if(typeof x == 'string') x = executeExpression(x) if(typeof x == 'string') x = Expr.executeExpression(x)
return ((this.openBegin && x > this.begin.execute()) || (!this.openBegin && x >= this.begin.execute())) && return ((this.openBegin && x > this.begin.execute()) || (!this.openBegin && x >= this.begin.execute())) &&
((this.openEnd && x < this.end.execute()) || (!this.openEnd && x <= this.end.execute())) ((this.openEnd && x < this.end.execute()) || (!this.openEnd && x <= this.end.execute()))
} }
@ -227,9 +214,9 @@ export class Range extends Domain {
} }
static import(frm) { static import(frm) {
let openBegin = frm.trim().charAt(0) === "]" var openBegin = frm.trim().charAt(0) == "]"
let openEnd = frm.trim().charAt(frm.length -1) === "[" var openEnd = frm.trim().charAt(frm.length -1) == "["
let [begin, end] = frm.substr(1, frm.length-2).split(";") var [begin, end] = frm.substr(1, frm.length-2).split(";")
return new Range(begin.trim(), end.trim(), openBegin, openEnd) return new Range(begin.trim(), end.trim(), openBegin, openEnd)
} }
} }
@ -237,16 +224,17 @@ export class Range extends Domain {
/** /**
* Domain classes for special domains (N, Z, ...) * Domain classes for special domains (N, Z, ...)
*/ */
export class SpecialDomain extends Domain { class SpecialDomain extends Domain {
/** /**
* @constructs SpecialDomain * @constructs SpecialDomain
* @param {string} displayName * @param {string} displayName
* @param {function} isValid - function returning true when number is in domain false when it isn't. * @param {function} isValid - function returning true when number is in domain false when it isn't.
* @param {function} next - function provides the next positive value in the domain after the one given. * @param {function} next - function provides the next positive value in the domain after the one given.
* @param {function} previous - function provides the previous positive value in the domain before the one given. * @param {function} previous - function provides the previous positive value in the domain before the one given.
* @param {boolean} moveSupported - Only true if next and previous functions are valid. * @param {bool} moveSupported - Only true if next and previous functions are valid.
* @param items
*/ */
constructor(displayName, isValid, next = () => true, previous = () => true, constructor(displayName, isValid, next = x => true, previous = x => true,
moveSupported = true) { moveSupported = true) {
super() super()
this.displayName = displayName this.displayName = displayName
@ -257,17 +245,17 @@ export class SpecialDomain extends Domain {
} }
includes(x) { includes(x) {
if(typeof x == 'string') x = executeExpression(x) if(typeof x == 'string') x = Expr.executeExpression(x)
return this.isValid(x) return this.isValid(x)
} }
next(x) { next(x) {
if(typeof x == 'string') x = executeExpression(x) if(typeof x == 'string') x = Expr.executeExpression(x)
return this.nextValue(x) return this.nextValue(x)
} }
previous(x) { previous(x) {
if(typeof x == 'string') x = executeExpression(x) if(typeof x == 'string') x = Expr.executeExpression(x)
return this.prevValue(x) return this.prevValue(x)
} }
@ -297,14 +285,14 @@ export class SpecialDomain extends Domain {
/** /**
* Domain classes for sets (e.g {0;3}, {0;1;2;pi} ...) * Domain classes for sets (e.g {0;3}, {0;1;2;pi} ...)
*/ */
export class DomainSet extends SpecialDomain { class DomainSet extends SpecialDomain {
constructor(values) { constructor(values) {
super('', x => true, x => x, true) super('', x => true, x => x, true)
let newVals = {} var newVals = {}
this.executedValues = [] this.executedValues = []
for(let value of values) { for(var value of values) {
let expr = new Expression(value.toString()) var expr = new Expr.Expression(value.toString())
let ex = expr.execute() var ex = expr.execute()
newVals[ex] = expr newVals[ex] = expr
this.executedValues.push(ex) this.executedValues.push(ex)
} }
@ -315,30 +303,30 @@ export class DomainSet extends SpecialDomain {
} }
includes(x) { includes(x) {
if(typeof x == 'string') x = executeExpression(x) if(typeof x == 'string') x = Expr.executeExpression(x)
for(let value of this.values) for(var value of this.values)
if(x === value.execute()) return true if(x == value.execute()) return true
return false return false
} }
next(x) { next(x) {
if(typeof x == 'string') x = executeExpression(x) if(typeof x == 'string') x = Expr.executeExpression(x)
if(x < this.executedValues[0]) return this.executedValues[0] if(x < this.executedValues[0]) return this.executedValues[0]
for(let i = 1; i < this.values.length; i++) { for(var i = 1; i < this.values.length; i++) {
let prevValue = this.executedValues[i-1] var prevValue = this.executedValues[i-1]
let value = this.executedValues[i] var value = this.executedValues[i]
if(x >= prevValue && x < value) return value if(x >= prevValue && x < value) return value
} }
return null return null
} }
previous(x) { previous(x) {
if(typeof x == 'string') x = executeExpression(x) if(typeof x == 'string') x = Expr.executeExpression(x)
if(x > this.executedValues[this.executedValues.length-1]) if(x > this.executedValues[this.executedValues.length-1])
return this.executedValues[this.executedValues.length-1] return this.executedValues[this.executedValues.length-1]
for(let i = 1; i < this.values.length; i++) { for(var i = 1; i < this.values.length; i++) {
let prevValue = this.executedValues[i-1] var prevValue = this.executedValues[i-1]
let value = this.executedValues[i] var value = this.executedValues[i]
if(x > prevValue && x <= value) return prevValue if(x > prevValue && x <= value) return prevValue
} }
return null return null
@ -351,56 +339,56 @@ export class DomainSet extends SpecialDomain {
union(domain) { union(domain) {
if(domain instanceof EmptySet) return this if(domain instanceof EmptySet) return this
if(domain instanceof DomainSet) { if(domain instanceof DomainSet) {
let newValues = [] var newValues = []
let values = this.values.concat(domain.values).filter(function(val){ var values = this.values.concat(domain.values).filter(function(val){
newValues.push(val.execute()) newValues.push(val.execute())
return newValues.indexOf(val.execute()) === newValues.length - 1 return newValues.indexOf(val.execute()) == newValues.length - 1
}) })
return new DomainSet(values) return new DomainSet(values)
} }
let notIncludedValues = [] var notIncludedValues = []
for(let i = 0; i < this.values.length; i++) { for(var value in this.values) {
let value = this.executedValues[i] var value = this.executedValues[i]
if(domain instanceof Range) { if(domain instanceof Range) {
if(domain.begin.execute() === value && domain.openBegin) { if(domain.begin.execute() == value && domain.openBegin) {
domain.openBegin = false domain.openBegin = false
} }
if(domain.end.execute() === value && domain.openEnd) { if(domain.end.execute() == value && domain.openEnd) {
domain.openEnd = false domain.openEnd = false
} }
} }
if(!domain.includes(value)) if(!domain.includes(value))
notIncludedValues.push(this.values[i].toEditableString()) notIncludedValues.push(this.values[i].toEditableString())
} }
if(notIncludedValues.length === 0) return domain if(notIncludedValues.length == 0) return domain
return new UnionDomain(domain, new DomainSet(notIncludedValues)) return new UnionDomain(domain, new DomainSet(notIncludedValues))
} }
intersection(domain) { intersection(domain) {
if(domain instanceof EmptySet) return domain if(domain instanceof EmptySet) return domain
if(domain instanceof DomainSet) { if(domain instanceof DomainSet) {
let domValues = domain.values.map(expr => expr.execute()) var domValues = domain.values.map(expr => expr.execute())
this.values = this.values.filter(function(val){ this.values = this.values.filter(function(val){
return domValues.indexOf(val.execute()) >= 0 return domValues.indexOf(val.execute()) >= 0
}) })
return this return this
} }
let includedValues = [] var includedValues = []
for(let i in this.values) { for(var i in this.values) {
let value = this.executedValues[i] var value = this.executedValues[i]
if(domain instanceof Range) { if(domain instanceof Range) {
if(domain.begin.execute() === value && !domain.openBegin) { if(domain.begin.execute() == value && !domain.openBegin) {
domain.openBegin = false domain.openBegin = false
} }
if(domain.end.execute() === value && !domain.openEnd) { if(domain.end.execute() == value && !domain.openEnd) {
domain.openEnd = false domain.openEnd = false
} }
} }
if(domain.includes(value)) if(domain.includes(value))
includedValues.push(this.values[i].toEditableString()) includedValues.push(this.values[i].toEditableString())
} }
if(includedValues.length === 0) return new EmptySet() if(includedValues.length == 0) return new EmptySet()
if(includedValues.length === this.values.length) return this if(includedValues.length == this.values.length) return this
return new IntersectionDomain(domain, new DomainSet(includedValues)) return new IntersectionDomain(domain, new DomainSet(includedValues))
} }
@ -412,7 +400,7 @@ export class DomainSet extends SpecialDomain {
/** /**
* Domain representing the union between two domains. * Domain representing the union between two domains.
*/ */
export class UnionDomain extends Domain { class UnionDomain extends Domain {
constructor(dom1, dom2) { constructor(dom1, dom2) {
super() super()
this.dom1 = dom1 this.dom1 = dom1
@ -447,10 +435,10 @@ export class UnionDomain extends Domain {
} }
static import(frm) { static import(frm) {
let domains = frm.trim().split("") var domains = frm.trim().split("")
if(domains.length === 1) domains = frm.trim().split("U") // Fallback if(domains.length == 1) domains = frm.trim().split("U") // Fallback
let dom2 = parseDomain(domains.pop()) var dom1 = parseDomain(domains.pop())
let dom1 = parseDomain(domains.join('')) var dom2 = parseDomain(domains.join(''))
return dom1.union(dom2) return dom1.union(dom2)
} }
} }
@ -458,7 +446,7 @@ export class UnionDomain extends Domain {
/** /**
* Domain representing the intersection between two domains. * Domain representing the intersection between two domains.
*/ */
export class IntersectionDomain extends Domain { class IntersectionDomain extends Domain {
constructor(dom1, dom2) { constructor(dom1, dom2) {
super() super()
this.dom1 = dom1 this.dom1 = dom1
@ -493,9 +481,9 @@ export class IntersectionDomain extends Domain {
} }
static import(frm) { static import(frm) {
let domains = frm.trim().split("∩") var domains = frm.trim().split("∩")
let dom1 = parseDomain(domains.pop()) var dom1 = parseDomain(domains.pop())
let dom2 = parseDomain(domains.join('∩')) var dom2 = parseDomain(domains.join('∩'))
return dom1.intersection(dom2) return dom1.intersection(dom2)
} }
} }
@ -503,7 +491,7 @@ export class IntersectionDomain extends Domain {
/** /**
* Domain representing the minus between two domains. * Domain representing the minus between two domains.
*/ */
export class MinusDomain extends Domain { class MinusDomain extends Domain {
constructor(dom1, dom2) { constructor(dom1, dom2) {
super() super()
this.dom1 = dom1 this.dom1 = dom1
@ -521,10 +509,10 @@ export class MinusDomain extends Domain {
} }
static import(frm) { static import(frm) {
let domains = frm.trim().split("") var domains = frm.trim().split("")
if(domains.length === 1) domains = frm.trim().split("\\") // Fallback if(domains.length == 1) domains = frm.trim().split("\\") // Fallback
let dom1 = parseDomain(domains.shift()) var dom1 = parseDomain(domains.shift())
let dom2 = parseDomain(domains.join('')) var dom2 = parseDomain(domains.join(''))
return new MinusDomain(dom1, dom2) return new MinusDomain(dom1, dom2)
} }
} }
@ -548,53 +536,53 @@ Domain.RPE.latexMarkup = "\\mathbb{R}^{+*}"
Domain.RME = new Range(-Infinity,0,true,true) Domain.RME = new Range(-Infinity,0,true,true)
Domain.RME.displayName = "ℝ⁻*" Domain.RME.displayName = "ℝ⁻*"
Domain.RME.latexMarkup = "\\mathbb{R}^{+*}" Domain.RME.latexMarkup = "\\mathbb{R}^{+*}"
Domain.N = new SpecialDomain('', x => x%1===0 && x >= 0, Domain.N = new SpecialDomain('', x => x%1==0 && x >= 0,
x => Math.max(Math.floor(x)+1, 0), x => Math.max(Math.floor(x)+1, 0),
x => Math.max(Math.ceil(x)-1, 0)) x => Math.max(Math.ceil(x)-1, 0))
Domain.N.latexMarkup = "\\mathbb{N}" Domain.N.latexMarkup = "\\mathbb{N}"
Domain.NE = new SpecialDomain('*', x => x%1===0 && x > 0, Domain.NE = new SpecialDomain('*', x => x%1==0 && x > 0,
x => Math.max(Math.floor(x)+1, 1), x => Math.max(Math.floor(x)+1, 1),
x => Math.max(Math.ceil(x)-1, 1)) x => Math.max(Math.ceil(x)-1, 1))
Domain.NE.latexMarkup = "\\mathbb{N}^{*}" Domain.NE.latexMarkup = "\\mathbb{N}^{*}"
Domain.Z = new SpecialDomain('', x => x%1===0, x => Math.floor(x)+1, x => Math.ceil(x)-1) Domain.Z = new SpecialDomain('', x => x%1==0, x => Math.floor(x)+1, x => Math.ceil(x)-1)
Domain.Z.latexMarkup = "\\mathbb{Z}" Domain.Z.latexMarkup = "\\mathbb{Z}"
Domain.ZE = new SpecialDomain('*', x => x%1===0 && x !== 0, Domain.ZE = new SpecialDomain('*', x => x%1==0 && x != 0,
x => Math.floor(x)+1 === 0 ? Math.floor(x)+2 : Math.floor(x)+1, x => Math.floor(x)+1 == 0 ? Math.floor(x)+2 : Math.floor(x)+1,
x => Math.ceil(x)-1 === 0 ? Math.ceil(x)-2 : Math.ceil(x)-1) x => Math.ceil(x)-1 == 0 ? Math.ceil(x)-2 : Math.ceil(x)-1)
Domain.ZE.latexMarkup = "\\mathbb{Z}^{*}" Domain.ZE.latexMarkup = "\\mathbb{Z}^{*}"
Domain.ZM = new SpecialDomain('ℤ⁻', x => x%1===0 && x <= 0, Domain.ZM = new SpecialDomain('ℤ⁻', x => x%1==0 && x <= 0,
x => Math.min(Math.floor(x)+1, 0), x => Math.min(Math.floor(x)+1, 0),
x => Math.min(Math.ceil(x)-1, 0)) x => Math.min(Math.ceil(x)-1, 0))
Domain.ZM.latexMarkup = "\\mathbb{Z}^{-}" Domain.ZM.latexMarkup = "\\mathbb{Z}^{-}"
Domain.ZME = new SpecialDomain('ℤ⁻*', x => x%1===0 && x < 0, Domain.ZME = new SpecialDomain('ℤ⁻*', x => x%1==0 && x < 0,
x => Math.min(Math.floor(x)+1, -1), x => Math.min(Math.floor(x)+1, -1),
x => Math.min(Math.ceil(x)-1, -1)) x => Math.min(Math.ceil(x)-1, -1))
Domain.ZME.latexMarkup = "\\mathbb{Z}^{-*}" Domain.ZME.latexMarkup = "\\mathbb{Z}^{-*}"
Domain.NLog = new SpecialDomain('ℕˡᵒᵍ', Domain.NLog = new SpecialDomain('ℕˡᵒᵍ',
x => x/Math.pow(10, x.toString().length-1) % 1 === 0 && x > 0, x => x/Math.pow(10, x.toString().length-1) % 1 == 0 && x > 0,
function(x) { function(x) {
let x10pow = Math.pow(10, x.toString().length-1) var x10pow = Math.pow(10, x.toString().length-1)
return Math.max(1, (Math.floor(x/x10pow)+1)*x10pow) return Math.max(1, (Math.floor(x/x10pow)+1)*x10pow)
}, },
function(x) { function(x) {
let x10pow = Math.pow(10, x.toString().length-1) var x10pow = Math.pow(10, x.toString().length-1)
return Math.max(1, (Math.ceil(x/x10pow)-1)*x10pow) return Math.max(1, (Math.ceil(x/x10pow)-1)*x10pow)
}) })
Domain.NLog.latexMarkup = "\\mathbb{N}^{log}" Domain.NLog.latexMarkup = "\\mathbb{N}^{log}"
let refedDomains = [] var refedDomains = []
/** /**
* Parses a domain, that can use parentheses. * Parses a domain, that can use parenthesises.
* e.g (N [-1;0[) (Z \ {0;3}) * e.g (N [-1;0[) (Z \ {0;3})
* @param {string} domain - string of the domain to be parsed. * @param {string} domain - string of the domain to be parsed.
* @returns {Domain} Parsed domain. * @returns {Domain} Parsed domain.
*/ */
export function parseDomain(domain) { function parseDomain(domain) {
if(!domain.includes(')') && !domain.includes('(')) return parseDomainSimple(domain) if(!domain.includes(')') && !domain.includes('(')) return parseDomainSimple(domain)
let domStr var domStr
while((domStr = /\(([^)(]+)\)/.exec(domain)) !== null) { while((domStr = /\(([^)(]+)\)/.exec(domain)) !== null) {
let dom = parseDomainSimple(domStr[1].trim()); var dom = parseDomainSimple(domStr[1].trim());
domain = domain.replace(domStr[0], 'D' + refedDomains.length) domain = domain.replace(domStr[0], 'D' + refedDomains.length)
refedDomains.push(dom) refedDomains.push(dom)
} }
@ -602,20 +590,20 @@ export function parseDomain(domain) {
} }
/** /**
* Parses a domain, without parentheses. * Parses a domain, without parenthesises.
* e.g N [-1;0[, Z \ {0;3}, N+*... * e.g N [-1;0[, Z \ {0;3}, N+*...
* @param {string} domain - string of the domain to be parsed. * @param {string} domain - string of the domain to be parsed.
* @returns {Domain} Parsed domain. * @returns {Domain} Parsed domain.
*/ */
export function parseDomainSimple(domain) { function parseDomainSimple(domain) {
domain = domain.trim() domain = domain.trim()
if(domain.includes("U") || domain.includes("")) return UnionDomain.import(domain) if(domain.includes("U") || domain.includes("")) return UnionDomain.import(domain)
if(domain.includes("∩")) return IntersectionDomain.import(domain) if(domain.includes("∩")) return IntersectionDomain.import(domain)
if(domain.includes("") || domain.includes("\\")) return MinusDomain.import(domain) if(domain.includes("") || domain.includes("\\")) return MinusDomain.import(domain)
if(domain.charAt(0) === "{" && domain.charAt(domain.length -1) === "}") return DomainSet.import(domain) if(domain.charAt(0) == "{" && domain.charAt(domain.length -1) == "}") return DomainSet.import(domain)
if(domain.includes("]") || domain.includes("[")) return Range.import(domain) if(domain.includes("]") || domain.includes("[")) return Range.import(domain)
if(["R", "", "N", "", "Z", ""].some(str => domain.toUpperCase().includes(str))) if(["R", "", "N", "", "Z", ""].some(str => domain.toUpperCase().includes(str)))
return Domain.import(domain) return Domain.import(domain)
if(domain[0] === 'D') return refedDomains[parseInt(domain.substr(1))] if(domain[0] == 'D') return refedDomains[parseInt(domain.substr(1))]
return new EmptySet() return new EmptySet()
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,25 +16,21 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
.pragma library
import Latex from "latex.mjs" .import "common.js" as C
import * as Utils from "../utils.mjs" .import "latex.js" as Latex
.import "../utils.js" as Utils
/** /**
* Represents any kind of x-based or non variable based expression. * Represents any kind of x-based or non variable based expression.
*/ */
export class Expression { class Expression {
constructor(expr) { constructor(expr) {
if(!Modules.ExprParser) this.expr = expr
throw new Error('Expression parser not initialized.') this.calc = C.parser.parse(expr).simplify()
if(!Modules.Objects)
throw new Error('Objects API not initialized.')
this.expr = Utils.exponentsToExpression(expr)
this.calc = Modules.ExprParser.parse(this.expr).simplify()
this.cached = this.isConstant() this.cached = this.isConstant()
this.cachedValue = null this.cachedValue = this.cached && this.allRequirementsFullfilled() ? this.calc.evaluate(C.currentObjectsByName) : null
if(this.cached && this.allRequirementsFullfilled())
this.cachedValue = this.calc.evaluate(Modules.Objects.currentObjectsByName)
this.latexMarkup = Latex.expression(this.calc.tokens) this.latexMarkup = Latex.expression(this.calc.tokens)
} }
@ -44,37 +40,37 @@ export class Expression {
} }
requiredObjects() { requiredObjects() {
return this.calc.variables().filter(objName => objName !== "x" && objName !== "n") return this.calc.variables().filter(objName => objName != "x" && objName != "n")
} }
allRequirementsFullfilled() { allRequirementsFullfilled() {
return this.requiredObjects().every(objName => objName in Modules.Objects.currentObjectsByName) return this.requiredObjects().every(objName => objName in C.currentObjectsByName)
} }
undefinedVariables() { undefinedVariables() {
return this.requiredObjects().filter(objName => !(objName in Modules.Objects.currentObjectsByName)) return this.requiredObjects().filter(objName => !(objName in C.currentObjectsByName))
} }
recache() { recache() {
if(this.cached) if(this.cached)
this.cachedValue = this.calc.evaluate(Modules.Objects.currentObjectsByName) this.cachedValue = this.calc.evaluate(C.currentObjectsByName)
} }
execute(x = 1) { execute(x = 1) {
if(this.cached) { if(this.cached) {
if(this.cachedValue == null) if(this.cachedValue == null)
this.cachedValue = this.calc.evaluate(Modules.Objects.currentObjectsByName) this.cachedValue = this.calc.evaluate(C.currentObjectsByName)
return this.cachedValue return this.cachedValue
} }
Modules.ExprParser.currentVars = Object.assign({'x': x}, Modules.Objects.currentObjectsByName) C.currentVars = Object.assign({'x': x}, C.currentObjectsByName)
return this.calc.evaluate(Modules.ExprParser.currentVars) return this.calc.evaluate(C.currentVars)
} }
simplify(x) { simplify(x) {
let expr = this.calc.substitute('x', x).simplify() var expr = this.calc.substitute('x', x).simplify()
if(expr.evaluate() === 0) return '0' if(expr.evaluate() == 0) return '0'
let str = Utils.makeExpressionReadable(expr.toString()); var str = Utils.makeExpressionReadable(expr.toString());
if(str !== undefined && str.match(/^\d*\.\d+$/)) { if(str != undefined && str.match(/^\d*\.\d+$/)) {
if(str.split('.')[1].split('0').length > 7) { if(str.split('.')[1].split('0').length > 7) {
// Likely rounding error // Likely rounding error
str = parseFloat(str.substring(0, str.length-1)).toString(); str = parseFloat(str.substring(0, str.length-1)).toString();
@ -93,11 +89,11 @@ export class Expression {
toString(forceSign=false) { toString(forceSign=false) {
let str = Utils.makeExpressionReadable(this.calc.toString()) let str = Utils.makeExpressionReadable(this.calc.toString())
if(str[0] !== '-' && forceSign) str = '+' + str if(str[0] != '-' && forceSign) str = '+' + str
return str return str
} }
} }
export function executeExpression(expr){ function executeExpression(expr){
return (new Expression(expr.toString())).execute() return (new Expression(expr.toString())).execute()
} }

View file

@ -0,0 +1,271 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 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":
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}';
break;
case "integral":
return '\\int\\limits_{' + args[0] + '}^{' + args[1] + '}' + args[2].substr(1, args[2].length-2) + ' d' + args[3].substr(1, args[3].length-2);
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 to convert
* @returns {string}
*/
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"]
function variable(vari) {
for(let i = 0; i < unicodechars.length; i++) {
//console.log(vari, unicodechars[i], equivalchars[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]);
}

View file

@ -1,288 +0,0 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Module } from '../modules.mjs'
const unicodechars = ["α","β","γ","δ","ε","ζ","η",
"π","θ","κ","λ","μ","ξ","ρ",
"ς","σ","τ","φ","χ","ψ","ω",
"Γ","Δ","Θ","Λ","Ξ","Π","Σ",
"Φ","Ψ","Ω","ₐ","ₑ","ₒ","ₓ",
"ₕ","ₖ","ₗ","ₘ","ₙ","ₚ","ₛ",
"ₜ","¹","²","³","⁴","⁵","⁶",
"⁷","⁸","⁹","⁰","₁","₂","₃",
"₄","₅","₆","₇","₈","₉","₀",
"pi", "∞"]
const 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"]
class LatexAPI extends Module {
constructor() {
super('Latex', [
/** @type {ExprParserAPI} */
Modules.ExprParser
])
/**
* true if latex has been enabled by the user, false otherwise.
*/
this.enabled = Helper.getSettingBool("enable_latex")
/**
* Mirror method for Python object.
* @type {function(string, number, string): string}.
*/
this.render = Latex.render
}
/**
* Puts element within parenthesis.
*
* @param {string} elem - element to put within parenthesis.
* @returns {string}
*/
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}
*/
parif(elem, contents) {
elem = elem.toString()
if(elem[0] !== "(" && elem[elem.length-1] !== ")" && contents.some(x => elem.indexOf(x) > 0))
return this.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 {(number,string)[]} args - Arguments of the function
* @returns {string}
*/
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 {boolean} wrapIn$ - checks whether the escaped chars should be escaped
* @returns {string}
*/
variable(vari, wrapIn$ = false) {
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}
*/
expression(tokens) {
let nstack = []
let n1, n2, n3
let f, args, argCount
for (let i = 0; i < tokens.length; i++) {
let item = tokens[i]
let type = item.type
switch(type) {
case Modules.ExprParser.Internals.INUMBER:
if(item.value === Infinity) {
nstack.push("\\infty")
} else if(typeof item.value === 'number' && item.value < 0) {
nstack.push(this.par(item.value));
} else if(Array.isArray(item.value)) {
nstack.push('[' + item.value.map(Modules.ExprParser.Internals.escapeValue).join(', ') + ']');
} else {
nstack.push(Modules.ExprParser.Internals.escapeValue(item.value));
}
break;
case Modules.ExprParser.Internals.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(this.par(n1) + f + this.par(n2));
break;
case '*':
if(n2 == "\\pi" || n2 == "e" || n2 == "x" || n2 == "n")
nstack.push(this.parif(n1,['+','-']) + n2)
else
nstack.push(this.parif(n1,['+','-']) + " \\times " + this.parif(n2,['+','-']));
break;
case '/':
nstack.push("\\frac{" + n1 + "}{" + n2 + "}");
break;
case '^':
nstack.push(this.parif(n1,['+','-','*','/','!']) + "^{" + n2 + "}");
break;
case '%':
nstack.push(this.parif(n1,['+','-','*','/','!','^']) + " \\mathrm{mod} " + parif(n2,['+','-','*','/','!','^']));
break;
case '[':
nstack.push(n1 + '[' + n2 + ']');
break;
default:
throw new EvalError("Unknown operator " + ope + ".");
}
break;
case Modules.ExprParser.Internals.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 Modules.ExprParser.Internals.IVAR:
case Modules.ExprParser.Internals.IVARNAME:
nstack.push(this.variable(item.value.toString()));
break;
case Modules.ExprParser.Internals.IOP1: // Unary operator
n1 = nstack.pop();
f = item.value;
switch(f) {
case '-':
case '+':
nstack.push(this.par(f + n1));
break;
case '!':
nstack.push(this.parif(n1,['+','-','*','/','^']) + '!');
break;
default:
nstack.push(f + this.parif(n1,['+','-','*','/','^']));
break;
}
break;
case Modules.ExprParser.Internals.IFUNCALL:
argCount = item.value;
args = [];
while (argCount-- > 0) {
args.unshift(nstack.pop());
}
f = nstack.pop();
// Handling various functions
nstack.push(this.functionToLatex(f, args))
break;
case Modules.ExprParser.Internals.IFUNDEF:
nstack.push(this.par(n1 + '(' + args.join(', ') + ') = ' + n2));
break;
case Modules.ExprParser.Internals.IMEMBER:
n1 = nstack.pop();
nstack.push(n1 + '.' + item.value);
break;
case Modules.ExprParser.Internals.IARRAY:
argCount = item.value;
args = [];
while (argCount-- > 0) {
args.unshift(nstack.pop());
}
nstack.push('[' + args.join(', ') + ']');
break;
case Modules.ExprParser.Internals.IEXPR:
nstack.push('(' + this.expression(item.value) + ')');
break;
case Modules.ExprParser.Internals.IENDSTATEMENT:
break;
default:
throw new EvalError('invalid Expression');
}
}
if (nstack.length > 1) {
nstack = [ nstack.join(';') ]
}
return String(nstack[0]);
}
}
/** @type {LatexAPI} */
Modules.Latex = Modules.Latex || new LatexAPI()
export default Modules.Latex

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,14 +16,18 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import * as Expr from "expression.mjs" .pragma library
import * as Utils from "../utils.mjs"
import Latex from "../math/latex.mjs" .import "common.js" as C
.import "expression.js" as Expr
.import "../utils.js" as Utils
.import "../math/latex.js" as Latex
/** /**
* Represents mathematical object for sequences. * Represents mathematical object for sequences.
*/ */
export class Sequence extends Expr.Expression { class Sequence extends Expr.Expression {
constructor(name, baseValues = {}, valuePlus = 1, expr = "") { constructor(name, baseValues = {}, valuePlus = 1, expr = "") {
// u[n+valuePlus] = expr // u[n+valuePlus] = expr
super(expr) super(expr)
@ -31,9 +35,9 @@ export class Sequence extends Expr.Expression {
this.baseValues = baseValues this.baseValues = baseValues
this.calcValues = Object.assign({}, baseValues) this.calcValues = Object.assign({}, baseValues)
this.latexValues = Object.assign({}, baseValues) this.latexValues = Object.assign({}, baseValues)
for(let n in this.calcValues) for(var n in this.calcValues)
if(['string', 'number'].includes(typeof this.calcValues[n])) { if(['string', 'number'].includes(typeof this.calcValues[n])) {
let parsed = Modules.ExprParser.parse(this.calcValues[n].toString()).simplify() let parsed = C.parser.parse(this.calcValues[n].toString()).simplify()
this.latexValues[n] = Latex.expression(parsed.tokens) this.latexValues[n] = Latex.expression(parsed.tokens)
this.calcValues[n] = parsed.evaluate() this.calcValues[n] = parsed.evaluate()
} }
@ -41,7 +45,7 @@ export class Sequence extends Expr.Expression {
} }
isConstant() { isConstant() {
return this.expr.indexOf("n") === -1 return this.expr.indexOf("n") == -1
} }
execute(n = 1) { execute(n = 1) {
@ -59,25 +63,21 @@ export class Sequence extends Expr.Expression {
} }
cache(n = 1) { cache(n = 1) {
let str = Utils.simplifyExpression(this.calc.substitute('n', n-this.valuePlus).toString()) var str = Utils.simplifyExpression(this.calc.substitute('n', n-this.valuePlus).toString())
let expr = Modules.ExprParser.parse(str).simplify() var expr = C.parser.parse(str).simplify()
// Cache values required for this one. C.currentVars = Object.assign(
if(!this.calcValues[n-this.valuePlus] && n-this.valuePlus > 0)
this.cache(n-this.valuePlus)
// Setting current variables
Modules.ExprParser.currentVars = Object.assign(
{'n': n-this.valuePlus}, // Just in case, add n (for custom functions) {'n': n-this.valuePlus}, // Just in case, add n (for custom functions)
Modules.Objects.currentObjectsByName, C.currentObjectsByName
{[this.name]: this.calcValues}
) )
this.calcValues[n] = expr.evaluate(Modules.ExprParser.currentVars) C.currentVars[this.name] = this.calcValues
this.calcValues[n] = expr.evaluate(C.currentVars)
} }
toString(forceSign=false) { toString(forceSign=false) {
let str = Utils.makeExpressionReadable(this.calc.toString()) var str = Utils.makeExpressionReadable(this.calc.toString())
if(str[0] !== '-' && forceSign) str = '+' + str if(str[0] != '-' && forceSign) str = '+' + str
let subtxt = this.valuePlus === 0 ? 'ₙ' : Utils.textsub('n+' + this.valuePlus) var subtxt = this.valuePlus == 0 ? 'ₙ' : Utils.textsub('n+' + this.valuePlus)
let ret = `${this.name}${subtxt} = ${str}${this.baseValues.length === 0 ? '' : "\n"}` var ret = `${this.name}${subtxt} = ${str}${this.baseValues.length == 0 ? '' : "\n"}`
ret += Object.keys(this.baseValues).map( ret += Object.keys(this.baseValues).map(
n => `${this.name}${Utils.textsub(n)} = ${this.baseValues[n]}` n => `${this.name}${Utils.textsub(n)} = ${this.baseValues[n]}`
).join('; ') ).join('; ')
@ -85,10 +85,10 @@ export class Sequence extends Expr.Expression {
} }
toLatexString(forceSign=false) { toLatexString(forceSign=false) {
let str = this.latexMarkup var str = this.latexMarkup
if(str[0] !== '-' && forceSign) str = '+' + str if(str[0] != '-' && forceSign) str = '+' + str
let subtxt = '_{n' + (this.valuePlus === 0 ? '' : '+' + this.valuePlus) + '}' var subtxt = '_{n' + (this.valuePlus == 0 ? '' : '+' + this.valuePlus) + '}'
let ret = `\\begin{array}{l}${Latex.variable(this.name)}${subtxt} = ${str}${this.latexValues.length === 0 ? '' : "\n"}\\\\` var ret = `\\begin{array}{l}${Latex.variable(this.name)}${subtxt} = ${str}${this.latexValues.length == 0 ? '' : "\n"}\\\\`
ret += Object.keys(this.latexValues).map( ret += Object.keys(this.latexValues).map(
n => `${this.name}_{${n}} = ${this.latexValues[n]}` n => `${this.name}_{${n}} = ${this.latexValues[n]}`
).join('; ') + "\\end{array}" ).join('; ') + "\\end{array}"

View file

@ -1,49 +1,41 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {BoolSetting} from "common.mjs" .pragma library
const CHECK_FOR_UPDATES = new BoolSetting( .import "math/expression.js" as Expr
QT_TR_NOOP("Check for updates on startup"), .import "math/sequence.js" as Seq
'check_for_updates',
'update'
)
const RESET_REDO_STACK = new BoolSetting( .import "math/domain.js" as Dom
QT_TR_NOOP("Reset redo stack automaticly"),
'reset_redo_stack',
'timeline'
)
class EnableLatex extends BoolSetting { var Expression = Expr.Expression
constructor() { var executeExpression = Expr.executeExpression
super(qsTr("Enable LaTeX rendering"), 'enable_latex', 'Expression') var Sequence = Seq.Sequence
}
set(value) { // Domains
super.set(value) var Domain = Dom.Domain
Modules.Latex.enabled = value var EmptySet = Dom.EmptySet
Modules.Canvas.requestPaint() var Range = Dom.Range
} var SpecialDomain = Dom.SpecialDomain
} var DomainSet = Dom.DomainSet
var UnionDomain = Dom.UnionDomain
var IntersectionDomain = Dom.IntersectionDomain
var MinusDomain = Dom.MinusDomain
export default [ var parseDomain = Dom.parseDomain
CHECK_FOR_UPDATES, var parseDomainSimple = Dom.parseDomainSimple
RESET_REDO_STACK,
new EnableLatex()
]

View file

@ -1,45 +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/>.
*/
/**
* Base class for global APIs in runtime.
*/
export class Module {
/**
*
* @param {string} name - Name of the API
* @param {(Module|undefined)[]} requires - List of APIs required to initialize this one.
*/
constructor(name, requires = []) {
console.log(`Loading module ${name}...`)
this.__check_requirements(requires, name)
}
/**
* Checks if all requirements are defined.
* @param {(Module|undefined)[]} requires
* @param {string} name
*/
__check_requirements(requires, name) {
for(let requirement of requires) {
if(requirement === undefined)
throw new Error(`Requirement ${requires.indexOf(requirement)} of ${name} has not been initialized.`)
}
}
}

View file

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

View file

@ -1,112 +0,0 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Module } from './modules.mjs'
class ObjectsAPI extends Module {
constructor() {
super('Objects')
this.types = {}
/**
* List of objects for each type of object.
* @type {Object.<string,DrawableObject[]>}
*/
this.currentObjects = {}
/**
* List of objects matched by their name.
* @type {Object.<string,DrawableObject>}
*/
this.currentObjectsByName = {}
}
/**
* 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.
*/
renameObject(oldName, newName) {
let obj = this.currentObjectsByName[oldName]
delete this.currentObjectsByName[oldName]
this.currentObjectsByName[newName] = obj
obj.name = newName
}
/**
* Deletes an object by its given name.
* @param {string} objName - Current name of the object.
*/
deleteObject(objName) {
let obj = this.currentObjectsByName[objName]
if(obj !== undefined) {
this.currentObjects[obj.type].splice(this.currentObjects[obj.type].indexOf(obj),1)
obj.delete()
delete this.currentObjectsByName[objName]
}
}
/**
* 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.
* @returns {string[]} List of names of the objects.
*/
getObjectsName(objType) {
if(objType === "ExecutableObject") {
// NOTE: QMLJS does not support flatMap.
// return getExecutableTypes().flatMap(elemType => currentObjects[elemType].map(obj => obj.name))
let types = this.getExecutableTypes()
let elementNames = ['']
for(let elemType of types)
elementNames = elementNames.concat(this.currentObjects[elemType].map(obj => obj.name))
return elementNames
}
if(this.currentObjects[objType] === undefined) return []
return this.currentObjects[objType].map(obj => obj.name)
}
/**
* Returns a list of all object types which are executable objects.
* @return {string[]} List of all object types which are executable objects.
*/
getExecutableTypes() {
return Object.keys(this.currentObjects).filter(objType => this.types[objType].executable())
}
/**
* 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 {DrawableObject<objType>} Newly created object.
*/
createNewRegisteredObject(objType, args= []) {
if(Object.keys(this.types).indexOf(objType) === -1) return null // Object type does not exist.
let newobj = new this.types[objType](...args)
if(Object.keys(this.currentObjects).indexOf(objType) === -1) {
this.currentObjects[objType] = []
}
this.currentObjects[objType].push(newobj)
this.currentObjectsByName[newobj.name] = newobj
return newobj
}
}
/** @type {ObjectsAPI} */
Modules.Objects = Modules.Objects || new ObjectsAPI()
export default Modules.Objects

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,25 +16,27 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
.pragma library
import * as Expr from "math/expression.mjs" .import "common.js" as C
import * as Seq from "math/sequence.mjs" .import "point.js" as P
import * as Dom from "math/domain.mjs" .import "text.js" as T
.import "function.js" as F
.import "gainbode.js" as GB
.import "phasebode.js" as PB
.import "sommegainsbode.js" as SGB
.import "sommephasesbode.js" as SPB
.import "xcursor.js" as X
.import "sequence.js" as S
.import "repartition.js" as R
C.registerObject(P.Point)
export const Expression = Expr.Expression C.registerObject(T.Text)
export const executeExpression = Expr.executeExpression C.registerObject(F.Function)
export const Sequence = Seq.Sequence C.registerObject(GB.GainBode)
C.registerObject(PB.PhaseBode)
// Domains C.registerObject(SGB.SommeGainsBode)
export const Domain = Dom.Domain C.registerObject(SPB.SommePhasesBode)
export const EmptySet = Dom.EmptySet C.registerObject(X.XCursor)
export const Range = Dom.Range C.registerObject(S.Sequence)
export const SpecialDomain = Dom.SpecialDomain C.registerObject(R.RepartitionFunction)
export const DomainSet = Dom.DomainSet
export const UnionDomain = Dom.UnionDomain
export const IntersectionDomain = Dom.IntersectionDomain
export const MinusDomain = Dom.MinusDomain
export const parseDomain = Dom.parseDomain
export const parseDomainSimple = Dom.parseDomainSimple

View file

@ -1,42 +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 { API as ObjectsCommonAPI } from "common.mjs"
import Point from "point.mjs"
import Text from "text.mjs"
import Function from "function.mjs"
import GainBode from "gainbode.mjs"
import PhaseBode from "phasebode.mjs"
import SommeGainsBode from "sommegainsbode.mjs"
import SommePhasesBode from "sommephasesbode.mjs"
import XCursor from "xcursor.mjs"
import Sequence from "sequence.mjs"
import RepartitionFunction from "repartition.mjs"
if(Object.keys(Modules.Objects.types).length === 0) {
ObjectsCommonAPI.registerObject(Point)
ObjectsCommonAPI.registerObject(Text)
ObjectsCommonAPI.registerObject(Function)
ObjectsCommonAPI.registerObject(GainBode)
ObjectsCommonAPI.registerObject(PhaseBode)
ObjectsCommonAPI.registerObject(SommeGainsBode)
ObjectsCommonAPI.registerObject(SommePhasesBode)
ObjectsCommonAPI.registerObject(XCursor)
ObjectsCommonAPI.registerObject(Sequence)
ObjectsCommonAPI.registerObject(RepartitionFunction)
}

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,72 +16,42 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { getRandomColor, textsub } from "../utils.mjs" .pragma library
import Objects from "../objects.mjs"
import Latex from "../math/latex.mjs" .import "../utils.js" as Utils
import {Module} from "../modules.mjs" .import "../objects.js" as Objects
import {ensureTypeSafety, serializesByPropertyType} from "../parameters.mjs" .import "../math/latex.js" as Latex
.import "../parameters.js" as P
.import "../math/common.js" as C
// This file contains the default data to be imported from all other objects // This file contains the default data to be imported from all other objects
class ObjectsCommonAPI extends Module { /**
* Creates a new name for an object, based on the \c allowedLetters.
constructor() { * If variables with each of the allowedLetters is created, a subscript
super('ObjectsCommon', [ * number is added to the name.
Modules.Objects, * @param {string} prefix - Prefix to the name.
Modules.ExprParser, * @return {string} New unused name for a new object.
Modules.Latex */
]) function getNewName(allowedLetters, prefix='') {
} // Allows to get a new name, based on the allowed letters,
// as well as adding a sub number when needs be.
/** var newid = 0
* Creates a new name for an object, based on the allowedLetters. var ret
* If variables with each of the allowedLetters is created, a subscript do {
* number is added to the name. var letter = allowedLetters[newid % allowedLetters.length]
* @param {string} allowedLetters var num = Math.floor((newid - (newid % allowedLetters.length)) / allowedLetters.length)
* @param {string} prefix - Prefix to the name. ret = prefix + letter + (num > 0 ? Utils.textsub(num-1) : '')
* @return {string} New unused name for a new object. newid += 1
*/ } while(ret in Objects.currentObjectsByName)
getNewName(allowedLetters, prefix='') { return ret
// Allows to get a new name, based on the allowed letters,
// as well as adding a sub number when needs be.
let newid = 0
let ret
do {
let letter = allowedLetters[newid % allowedLetters.length]
let num = Math.floor((newid - (newid % allowedLetters.length)) / allowedLetters.length)
ret = prefix + letter + (num > 0 ? textsub(num-1) : '')
newid += 1
} while(ret in Objects.currentObjectsByName)
return ret
}
/**
* Registers the object obj in the object list.
* @param {DrawableObject} obj - Object to be registered.
*/
registerObject(obj) {
// Registers an object to be used in LogarithmPlotter.
// This function is called from autoload.mjs
if(obj.prototype instanceof DrawableObject) {
if(!Objects.types[obj.type()])
Objects.types[obj.type()] = obj
} else {
console.error("Could not register object " + (obj.type()) + ", as it isn't a DrawableObject.")
}
}
} }
/** @type {ObjectsCommonAPI} */
Modules.ObjectsCommon = Modules.ObjectsCommon || new ObjectsCommonAPI()
export const API = Modules.ObjectsCommon
/** /**
* Class to extend for every type of object that * Class to extend for every type of object that
* can be drawn on the canvas. * can be drawn on the canvas.
*/ */
export class DrawableObject { class DrawableObject {
/** /**
* Base name of the object. Needs to be constant over time. * Base name of the object. Needs to be constant over time.
* @return {string} Type of the object. * @return {string} Type of the object.
@ -103,7 +73,7 @@ export class DrawableObject {
/** /**
* True if this object can be created by the user, false if it can only * True if this object can be created by the user, false if it can only
* be instantiated by other objects * be instantiated by other objects
* @return {boolean} * @return {bool}
*/ */
static createable() {return true} static createable() {return true}
@ -119,44 +89,30 @@ export class DrawableObject {
* If the property is to be translated, the key should be passed * If the property is to be translated, the key should be passed
* through the QT_TRANSLATE_NOOP macro in that form: * through the QT_TRANSLATE_NOOP macro in that form:
* [QT_TRANSLATE_NOOP('prop','key')] * [QT_TRANSLATE_NOOP('prop','key')]
* Enums that are translated should be indexed in parameters.mjs and * Enums that are translated should be indexed in parameters.js and
* then be linked directly here. * then be linked directly here.
* *
* @return {Object.<string,string|Enum|List|ObjectType|Dictionary>} * @return {Dictionary}
*/ */
static properties() {return {}} static properties() {return {}}
/** /**
* True if this object can be executed, so that an y value might be computed * True if this object can be executed, so that an y value might be computed
* for an x value. Only ExecutableObjects have that property set to true. * for an x value. Only ExecutableObjects have that property set to true.
* @return {boolean} * @return {bool}
*/ */
static executable() {return false} static executable() {return false}
/**
* Imports the object from its serialized form.
* @return {DrawableObject}
*/
static import(name, visible, color, labelContent, ...args) {
let importedArgs = [name.toString(), visible === true, color.toString(), labelContent]
console.log('Importing', this.type(), name, args)
for(let [name, propType] of Object.entries(this.properties()))
if(!name.startsWith('comment')) {
importedArgs.push(ensureTypeSafety(propType, args[importedArgs.length-4]))
}
return new this(...importedArgs)
}
/** /**
* Base constructor for the object. * Base constructor for the object.
* @param {string} name - Name of the object * @param {string} name - Name of the object
* @param {boolean} visible - true if the object is visible, false otherwise. * @param {bool} visible - true if the object is visible, false otherwise.
* @param {color|string} color - Color of the object (can be string or QColor) * @param {color} color - Color of the object (can be string or QColor)
* @param {Enum} labelContent - One of 'null', 'name' or 'name + value' describing the content of the label. * @param {Enum} labelContent - One of 'null', 'name' or 'name + value' describing the content of the label.
* @constructor * @constructor()
*/ */
constructor(name, visible = true, color = null, labelContent = 'name + value') { constructor(name, visible = true, color = null, labelContent = 'name + value') {
if(color == null) color = getRandomColor() if(color == null) color = Utils.getRandomColor()
this.type = this.constructor.type() this.type = this.constructor.type()
this.name = name this.name = name
this.visible = visible this.visible = visible
@ -165,18 +121,15 @@ export class DrawableObject {
this.requiredBy = [] this.requiredBy = []
this.requires = [] this.requires = []
} }
/** /**
* Serializes the object in an array that can be JSON serialized. * Serilizes the object in an array that can be JSON serialized.
* These parameters will be re-entered in the constructor when restored. * These parameters will be re-entered in the constructor when restored.
* @return {array} * @return {array}
*/ */
export() { export() {
let exportList = [this.name, this.visible, this.color.toString(), this.labelContent] // Should return what will be inputed as arguments when a file is loaded (serializable form)
for(let [name, propType] of Object.entries(this.constructor.properties())) return [this.name, this.visible, this.color.toString(), this.labelContent]
if(!name.startsWith('comment'))
exportList.push(serializesByPropertyType(propType, this[name]))
return exportList
} }
/** /**
@ -192,7 +145,7 @@ export class DrawableObject {
* Latex markuped version of the readable string. * Latex markuped version of the readable string.
* Every non latin character should be passed as latex symbols and formulas * Every non latin character should be passed as latex symbols and formulas
* should be in latex form. * should be in latex form.
* See ../latex.mjs for helper methods. * See ../latex.js for helper methods.
* @return {string} * @return {string}
*/ */
getLatexString() { getLatexString() {
@ -200,7 +153,7 @@ export class DrawableObject {
} }
/** /**
* Readable string content of the label depending on the value of the latexContent. * Readable string content of the label depending on the value of the \c latexContent.
* @return {string} * @return {string}
*/ */
getLabel() { getLabel() {
@ -216,10 +169,10 @@ export class DrawableObject {
} }
/** /**
* Latex markup string content of the label depending on the value of the latexContent. * Latex markup string content of the label depending on the value of the \c latexContent.
* Every non latin character should be passed as latex symbols and formulas * Every non latin character should be passed as latex symbols and formulas
* should be in latex form. * should be in latex form.
* See ../latex.mjs for helper methods. * See ../latex.js for helper methods.
* @return {string} * @return {string}
*/ */
getLatexLabel() { getLatexLabel() {
@ -251,26 +204,25 @@ export class DrawableObject {
update() { update() {
// Refreshing dependencies. // Refreshing dependencies.
for(let obj of this.requires) for(let obj of this.requires)
obj.requiredBy = obj.requiredBy.filter(dep => dep !== this) obj.requiredBy = obj.requiredBy.filter(dep => dep != this)
// Checking objects this one depends on // Checking objects this one depends on
this.requires = [] this.requires = []
let currentObjectsByName = Objects.currentObjectsByName
let properties = this.constructor.properties() let properties = this.constructor.properties()
for(let property in properties) for(let property in properties)
if(typeof properties[property] == 'object' && 'type' in properties[property]) if(typeof properties[property] == 'object' && 'type' in properties[property])
if(properties[property].type === 'Expression' && this[property] != null) { if(properties[property].type == 'Expression' && this[property] != null) {
// Expressions with dependencies // Expressions with dependencies
for(let objName of this[property].requiredObjects()) { for(let objName of this[property].requiredObjects()) {
if(objName in currentObjectsByName && !this.requires.includes(currentObjectsByName[objName])) { if(objName in C.currentObjectsByName && !this.requires.includes(C.currentObjectsByName[objName])) {
this.requires.push(currentObjectsByName[objName]) this.requires.push(C.currentObjectsByName[objName])
currentObjectsByName[objName].requiredBy.push(this) C.currentObjectsByName[objName].requiredBy.push(this)
} }
} }
if(this[property].cached && this[property].requiredObjects().length > 0) if(this[property].cached && this[property].requiredObjects().length > 0)
// Recalculate // Recalculate
this[property].recache() this[property].recache()
} else if(properties[property].type === 'ObjectType' && this[property] != null) { } else if(properties[property].type == 'ObjectType' && this[property] != null) {
// Object dependency // Object dependency
this.requires.push(this[property]) this.requires.push(this[property])
this[property].requiredBy.push(this) this[property].requiredBy.push(this)
@ -287,26 +239,24 @@ export class DrawableObject {
for(let toRemove of this.requiredBy) { // Normally, there should be none here, but better leave nothing just in case. for(let toRemove of this.requiredBy) { // Normally, there should be none here, but better leave nothing just in case.
Objects.deleteObject(toRemove.name) Objects.deleteObject(toRemove.name)
} }
for(let toRemoveFrom of this.requires) {
toRemoveFrom.requiredBy = toRemoveFrom.requiredBy.filter(o => o !== this)
}
} }
/** /**
* Abstract method. Draw the object onto the canvas with the. * Abstract method. Draw the object onto the \c canvas with the 2D context \c ctx.
* @param {CanvasAPI} canvas * @param {Canvas} canvas
* @param {Context2D} ctx
*/ */
draw(canvas) {} draw(canvas, ctx) {}
/** /**
* Applicates a drawFunction with two position arguments depending on * Applicates a \c drawFunction with two position arguments depending on
* both the posX and posY of where the label should be displayed, * both the \c posX and \c posY of where the label should be displayed,
* and the labelPosition which declares the label should be displayed * and the \c labelPosition which declares the label should be displayed
* relatively to that position. * relatively to that position.
* *
* @param {string|Enum} labelPosition - Position of the label relative to the marked position * @param {string|Enum} labelPosition - Position of the label relative to the marked position
* @param {number} offset - Margin between the position and the object to be drawn * @param {number} offset - Margin between the position and the object to be drawn
* @param {Object.<string, int>} size - Size of the label item, containing two properties, "width" and "height" * @param {Dictionary} size - Size of the label item, containing two properties, "width" and "height"
* @param {number} posX - Component of the marked position on the x-axis * @param {number} posX - Component of the marked position on the x-axis
* @param {number} posY - Component of the marked position on the y-axis * @param {number} posY - Component of the marked position on the y-axis
* @param {function} drawFunction - Function with two arguments (x, y) that will be called to draw the label * @param {function} drawFunction - Function with two arguments (x, y) that will be called to draw the label
@ -350,26 +300,27 @@ export class DrawableObject {
} }
/** /**
* Automatically draw text (by default the label of the object on the canvas * Automatically draw text (by default the label of the object on the \c canvas with
* depending on user settings. * the 2D context \c ctx depending on user settings.
* This method takes into account both the posX and posY of where the label * This method takes into account both the \c posX and \c posY of where the label
* should be displayed, including the labelPosition relative to it. * should be displayed, including the \c labelPosition relative to it.
* The text is get both through the getLatexFunction and getTextFunction * The text is get both through the \c getLatexFunction and \c getTextFunction
* depending on whether to use latex. * depending on whether to use latex.
* Then, it's displayed using the drawFunctionLatex (x,y,imageData) and * Then, it's displayed using the \c drawFunctionLatex (x,y,imageData) and
* drawFunctionText (x,y,text) depending on whether to use latex. * \c drawFunctionText (x,y,text) depending on whether to use latex.
* *
* @param {CanvasAPI} canvas * @param {Canvas} canvas
* @param {Context2D} ctx
* @param {string|Enum} labelPosition - Position of the label relative to the marked position * @param {string|Enum} labelPosition - Position of the label relative to the marked position
* @param {number} posX - Component of the marked position on the x-axis * @param {number} posX - Component of the marked position on the x-axis
* @param {number} posY - Component of the marked position on the y-axis * @param {number} posY - Component of the marked position on the y-axis
* @param {boolean} forceText - Force the rendering of the label as text * @param {bool} forceText - Force the rendering of the label as text
* @param {function|null} getLatexFunction - Function (no argument) to get the latex markup to be displayed * @param {function|null} getLatexFunction - Function (no argument) to get the latex markup to be displayed
* @param {function|null} getTextFunction - Function (no argument) to get the text to be displayed * @param {function|null} getTextFunction - Function (no argument) to get the text to be displayed
* @param {function|null} drawFunctionLatex - Function (x,y,imageData) to display the latex image * @param {function|null} drawFunctionLatex - Function (x,y,imageData) to display the latex image
* @param {function|null} drawFunctionText - Function (x,y,text,textSize) to display the text * @param {function|null} drawFunctionText - Function (x,y,text,textSize) to display the text
*/ */
drawLabel(canvas, labelPosition, posX, posY,forceText = false, drawLabel(canvas, ctx, labelPosition, posX, posY, forceText = false,
getLatexFunction = null, getTextFunction = null, drawFunctionLatex = null, drawFunctionText = null) { getLatexFunction = null, getTextFunction = null, drawFunctionLatex = null, drawFunctionText = null) {
// Default functions // Default functions
if(getLatexFunction == null) if(getLatexFunction == null)
@ -377,25 +328,25 @@ export class DrawableObject {
if(getTextFunction == null) if(getTextFunction == null)
getTextFunction = this.getLabel.bind(this) getTextFunction = this.getLabel.bind(this)
if(drawFunctionLatex == null) if(drawFunctionLatex == null)
drawFunctionLatex = (x,y,ltxImg) => canvas.drawVisibleImage(ltxImg.source, x, y, ltxImg.width, ltxImg.height) drawFunctionLatex = (x,y,ltxImg) => canvas.drawVisibleImage(ctx, ltxImg.source, x, y, ltxImg.width, ltxImg.height)
if(drawFunctionText == null) if(drawFunctionText == null)
drawFunctionText = (x,y,text,textSize) => canvas.drawVisibleText(text, x, y+textSize.height) // Positioned from left bottom drawFunctionText = (x,y,text,textSize) => canvas.drawVisibleText(ctx, text, x, y+textSize.height) // Positioned from left bottom
// Drawing the label // Drawing the label
let offset let offset
if(!forceText && Latex.enabled) { if(!forceText && Latex.enabled) { // TODO: Check for user setting with Latex.
// With latex // With latex
let drawLblCb = ((ltxImg) => { let drawLblCb = function(canvas, ctx, ltxImg) {
this.drawPositionDivergence(labelPosition, 8+canvas.linewidth/2, ltxImg, posX, posY, (x,y) => drawFunctionLatex(x,y,ltxImg)) this.drawPositionDivergence(labelPosition, 8+ctx.lineWidth/2, ltxImg, posX, posY, (x,y) => drawFunctionLatex(x,y,ltxImg))
}).bind(this) }
let ltxLabel = getLatexFunction(); let ltxLabel = getLatexFunction();
if(ltxLabel !== "") if(ltxLabel != "")
canvas.renderLatexImage(ltxLabel, this.color, drawLblCb.bind(this)) canvas.renderLatexImage(ltxLabel, this.color, drawLblCb.bind(this))
} else { } else {
// Without latex // Without latex
let text = getTextFunction() let text = getTextFunction()
canvas.font = `${canvas.textsize}px sans-serif` ctx.font = `${canvas.textsize}px sans-serif`
let textSize = canvas.measureText(text) let textSize = canvas.measureText(ctx, text)
this.drawPositionDivergence(labelPosition, 8+canvas.linewidth/2, textSize, posX, posY, (x,y) => drawFunctionText(x,y,text,textSize)) this.drawPositionDivergence(labelPosition, 8+ctx.lineWidth/2, textSize, posX, posY, (x,y) => drawFunctionText(x,y,text,textSize))
} }
} }
@ -404,7 +355,6 @@ export class DrawableObject {
} }
} }
/** /**
* Class to be extended for every object on which * Class to be extended for every object on which
* an y for a x can be computed with the execute function. * an y for a x can be computed with the execute function.
@ -412,7 +362,7 @@ export class DrawableObject {
* return null. However, theses same x values will * return null. However, theses same x values will
* return false when passed to canExecute. * return false when passed to canExecute.
*/ */
export class ExecutableObject extends DrawableObject { class ExecutableObject extends DrawableObject {
/** /**
* Returns the corresponding y value for an x value. * Returns the corresponding y value for an x value.
* If the object isn't defined on the given x, then * If the object isn't defined on the given x, then
@ -426,25 +376,38 @@ export class ExecutableObject extends DrawableObject {
* Returns false if the object isn't defined on the given x, true otherwise. * Returns false if the object isn't defined on the given x, true otherwise.
* *
* @param {number} x * @param {number} x
* @returns {boolean} * @returns {bool}
*/ */
canExecute(x = 1) {return true} canExecute(x = 1) {return true}
/** /**
* Returns the simplified expression string for a given x. * Returns the simplified expression string for a given x.
* *
* @param {number} x * @param {number} x
* @returns {string} * @returns {bool}
*/ */
simplify(x = 1) {return '0'} simplify(x = 1) {return '0'}
/** /**
* True if this object can be executed, so that an y value might be computed * True if this object can be executed, so that an y value might be computed
* for an x value. Only ExecutableObjects have that property set to true. * for an x value. Only ExecutableObjects have that property set to true.
* @return {boolean} * @return {bool}
*/ */
static executable() {return true} static executable() {return true}
} }
/**
* Registers the object \c obj in the object list.
* @param {DrawableObject} obj - Object to be registered.
*/
function registerObject(obj) {
// Registers an object to be used in LogarithmPlotter.
// This function is called from autoload.js
if(obj.prototype instanceof DrawableObject) {
Objects.types[obj.type()] = obj
} else {
console.error("Could not register object " + (obj.type()) + ", as it isn't a DrawableObject.")
}
}

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,14 +16,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { textsub } from "../utils.mjs" .pragma library
import { API as Common, ExecutableObject } from "common.mjs"
import { parseDomain, Expression, SpecialDomain } from "../mathlib.mjs" .import "common.js" as Common
import * as P from "../parameters.mjs" .import "../utils.js" as Utils
import Latex from "../math/latex.mjs" .import "../mathlib.js" as MathLib
.import "../parameters.js" as P
.import "../math/latex.js" as Latex
export default class Function extends ExecutableObject { class Function extends Common.ExecutableObject {
static type(){return 'Function'} static type(){return 'Function'}
static displayType(){return qsTr('Function')} static displayType(){return qsTr('Function')}
static displayTypeMultiple(){return qsTr('Functions')} static displayTypeMultiple(){return qsTr('Functions')}
@ -35,8 +37,8 @@ export default class Function extends ExecutableObject {
'comment', 'comment',
'Ex: R+* (ℝ⁺*), N (), Z-* (ℤ⁻*), ]0;1[, {3;4;5}' 'Ex: R+* (ℝ⁺*), N (), Z-* (ℤ⁻*), ]0;1[, {3;4;5}'
), ),
[QT_TRANSLATE_NOOP('prop','displayMode')]: P.Enum.FunctionDisplayType,
[QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position, [QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position,
[QT_TRANSLATE_NOOP('prop','displayMode')]: P.Enum.FunctionDisplayType,
[QT_TRANSLATE_NOOP('prop','labelX')]: 'number', [QT_TRANSLATE_NOOP('prop','labelX')]: 'number',
'comment2': QT_TRANSLATE_NOOP( 'comment2': QT_TRANSLATE_NOOP(
'comment', 'comment',
@ -52,11 +54,11 @@ export default class Function extends ExecutableObject {
drawPoints = true, drawDashedLines = true) { drawPoints = true, drawDashedLines = true) {
if(name == null) name = Common.getNewName('fghjqlmnopqrstuvwabcde') if(name == null) name = Common.getNewName('fghjqlmnopqrstuvwabcde')
super(name, visible, color, labelContent) super(name, visible, color, labelContent)
if(typeof expression == 'number' || typeof expression == 'string') expression = new Expression(expression.toString()) if(typeof expression == 'number' || typeof expression == 'string') expression = new MathLib.Expression(expression.toString())
this.expression = expression this.expression = expression
if(typeof definitionDomain == 'string') definitionDomain = parseDomain(definitionDomain) if(typeof definitionDomain == 'string') definitionDomain = MathLib.parseDomain(definitionDomain)
this.definitionDomain = definitionDomain this.definitionDomain = definitionDomain
if(typeof destinationDomain == 'string') destinationDomain = parseDomain(destinationDomain) if(typeof destinationDomain == 'string') destinationDomain = MathLib.parseDomain(destinationDomain)
this.destinationDomain = destinationDomain this.destinationDomain = destinationDomain
this.displayMode = displayMode this.displayMode = displayMode
this.labelPosition = labelPosition this.labelPosition = labelPosition
@ -66,22 +68,28 @@ export default class Function extends ExecutableObject {
} }
getReadableString() { getReadableString() {
if(this.displayMode === 'application') { if(this.displayMode == 'application') {
return `${this.name}: ${this.definitionDomain}${this.destinationDomain}\n ${' '.repeat(this.name.length)}x ⟼ ${this.expression.toString()}` return `${this.name}: ${this.definitionDomain}${this.destinationDomain}\n ${' '.repeat(this.name.length)}x ⟼ ${this.expression.toString()}`
} else { } else {
return `${this.name}(x) = ${this.expression.toString()}\nD${textsub(this.name)} = ${this.definitionDomain}` return `${this.name}(x) = ${this.expression.toString()}\nD${Utils.textsub(this.name)} = ${this.definitionDomain}`
} }
} }
getLatexString() { getLatexString() {
if(this.displayMode === 'application') { if(this.displayMode == 'application') {
return `${Latex.variable(this.name)}:\\begin{array}{llll}${this.definitionDomain.latexMarkup}\\textrm{ } & \\rightarrow & \\textrm{ }${this.destinationDomain.latexMarkup}\\\\ return `${Latex.variable(this.name)}:\\begin{array}{llll}${this.definitionDomain.latexMarkup}\\textrm{ } & \\rightarrow & \\textrm{ }${this.destinationDomain.latexMarkup}\\\\
x\\textrm{ } & \\mapsto & \\textrm{ }${this.expression.latexMarkup}\\end{array}` x\\textrm{ } & \\mapsto & \\textrm{ }${this.expression.latexMarkup}\\end{array}`
} else { } else {
return `\\begin{array}{l}${Latex.variable(this.name)}(x) = ${this.expression.latexMarkup}\\\\ D_{${this.name}} = ${this.definitionDomain.latexMarkup}\\end{array}` return `\\begin{array}{l}${Latex.variable(this.name)}(x) = ${this.expression.latexMarkup}\\\\ D_{${this.name}} = ${this.definitionDomain.latexMarkup}\\end{array}`
} }
} }
export() {
return [this.name, this.visible, this.color.toString(), this.labelContent,
this.expression.toEditableString(), this.definitionDomain.toString(), this.destinationDomain.toString(),
this.displayMode, this.labelPosition, this.labelX, this.drawPoints, this.drawDashedLines]
}
execute(x = 1) { execute(x = 1) {
if(this.definitionDomain.includes(x)) if(this.definitionDomain.includes(x))
return this.expression.execute(x) return this.expression.execute(x)
@ -98,38 +106,36 @@ export default class Function extends ExecutableObject {
return '' return ''
} }
draw(canvas) { draw(canvas, ctx) {
Function.drawFunction(canvas, this.expression, this.definitionDomain, this.destinationDomain, this.drawPoints, this.drawDashedLines) Function.drawFunction(canvas, ctx, this.expression, this.definitionDomain, this.destinationDomain, this.drawPoints, this.drawDashedLines)
// Label // Label
this.drawLabel(canvas, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX))) this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))
} }
/** static drawFunction(canvas, ctx, expr, definitionDomain, destinationDomain, drawPoints = true, drawDash = true) {
* Reusable in other objects. // Reusable in other objects.
* Drawing small traits every few pixels // Drawing small traits every 0.2px
*/ var pxprecision = 1
static drawFunction(canvas, expr, definitionDomain, destinationDomain, drawPoints = true, drawDash = true) { var previousX = canvas.px2x(0)
let pxprecision = 10 var previousY;
let previousX = canvas.px2x(0) if(definitionDomain instanceof MathLib.SpecialDomain && definitionDomain.moveSupported) {
let previousY = null;
if(definitionDomain instanceof SpecialDomain && definitionDomain.moveSupported) {
// Point based functions. // Point based functions.
previousX = definitionDomain.next(previousX) previousX = definitionDomain.next(previousX)
if(previousX === null) previousX = definitionDomain.next(canvas.px2x(0)) if(previousX === null) previousX = definitionDomain.next(canvas.px2x(0))
previousY = expr.execute(previousX) previousY = expr.execute(previousX)
if(!drawPoints && !drawDash) return if(!drawPoints && !drawDash) return
while(previousX !== null && canvas.x2px(previousX) < canvas.width) { while(previousX !== null && canvas.x2px(previousX) < canvas.canvasSize.width) {
// Reconverted for canvas to fix for logarithmic scales. // Reconverted for canvas to fix for logarithmic scales.
let currentX = definitionDomain.next(canvas.px2x(canvas.x2px(previousX)+pxprecision)); var currentX = definitionDomain.next(canvas.px2x(canvas.x2px(previousX)+pxprecision));
let currentY = expr.execute(currentX) var currentY = expr.execute(currentX)
if(currentX === null) break; if(currentX === null) break;
if((definitionDomain.includes(currentX) || definitionDomain.includes(previousX)) && if((definitionDomain.includes(currentX) || definitionDomain.includes(previousX)) &&
(destinationDomain.includes(currentY) || destinationDomain.includes(previousY))) { (destinationDomain.includes(currentY) || destinationDomain.includes(previousY))) {
if(drawDash) if(drawDash)
canvas.drawDashedLine(canvas.x2px(previousX), canvas.y2px(previousY), canvas.x2px(currentX), canvas.y2px(currentY)) canvas.drawDashedLine(ctx, canvas.x2px(previousX), canvas.y2px(previousY), canvas.x2px(currentX), canvas.y2px(currentY))
if(drawPoints) { if(drawPoints) {
canvas.fillRect(canvas.x2px(previousX)-5, canvas.y2px(previousY)-1, 10, 2) ctx.fillRect(canvas.x2px(previousX)-5, canvas.y2px(previousY)-1, 10, 2)
canvas.fillRect(canvas.x2px(previousX)-1, canvas.y2px(previousY)-5, 2, 10) ctx.fillRect(canvas.x2px(previousX)-1, canvas.y2px(previousY)-5, 2, 10)
} }
} }
previousX = currentX previousX = currentX
@ -137,52 +143,21 @@ export default class Function extends ExecutableObject {
} }
if(drawPoints) { if(drawPoints) {
// Drawing the last cross // Drawing the last cross
canvas.fillRect(canvas.x2px(previousX)-5, canvas.y2px(previousY)-1, 10, 2) ctx.fillRect(canvas.x2px(previousX)-5, canvas.y2px(previousY)-1, 10, 2)
canvas.fillRect(canvas.x2px(previousX)-1, canvas.y2px(previousY)-5, 2, 10) ctx.fillRect(canvas.x2px(previousX)-1, canvas.y2px(previousY)-5, 2, 10)
} }
} else { } else {
// Use max precision if function is trigonometrical on log scale. previousY = expr.execute(previousX)
let exprString = expr.expr for(var px = pxprecision; px < canvas.canvasSize.width; px += pxprecision) {
if(exprString.includes("sin") || exprString.includes("cos") || exprString.includes("tan")) var currentX = canvas.px2x(px)
pxprecision = (canvas.logscalex || exprString.includes("tan")) ? 1 : 3 var currentY = expr.execute(currentX)
// Calculate the previousY at the start of the canvas if((definitionDomain.includes(currentX) || definitionDomain.includes(previousX)) &&
if(definitionDomain.includes(previousX)) (destinationDomain.includes(currentY) || destinationDomain.includes(previousY)) &&
previousY = expr.execute(previousX) Math.abs(previousY-currentY)<100) {
for(let px = pxprecision; px < canvas.width; px += pxprecision) { canvas.drawLine(ctx, canvas.x2px(previousX), canvas.y2px(previousY), canvas.x2px(currentX), canvas.y2px(currentY))
let currentX = canvas.px2x(px)
if(!definitionDomain.includes(previousX) && definitionDomain.includes(currentX)) {
// Should draw up to currentX, but NOT at previousX.
// Need to find the starting point.
let tmpPx = px-pxprecision
do {
tmpPx++;
previousX = canvas.px2x(tmpPx)
} while(!definitionDomain.includes(previousX))
// Recaclulate previousY
previousY = expr.execute(previousX)
} else if(!definitionDomain.includes(currentX)) {
// Next x is NOT in the definition domain.
// Augmenting the pixel precision until this condition is fulfilled.
let tmpPx = px
do {
tmpPx--;
currentX = canvas.px2x(tmpPx)
} while(!definitionDomain.includes(currentX) && currentX !== previousX)
}
// This max variation is needed for functions with asymptotical vertical lines (e.g. 1/x, tan x...)
let maxvariation = (canvas.px2y(0)-canvas.px2y(canvas.height))
if(definitionDomain.includes(previousX) && definitionDomain.includes(currentX)) {
let currentY = expr.execute(currentX)
if(destinationDomain.includes(currentY)) {
if(previousY != null && destinationDomain.includes(previousY) && Math.abs(previousY-currentY) < maxvariation) {
canvas.drawLine(canvas.x2px(previousX), canvas.y2px(previousY), canvas.x2px(currentX), canvas.y2px(currentY))
}
} }
previousY = currentY previousX = currentX
} else { previousY = currentY
previousY = null // Last y was invalid, so let's not draw anything from it.
}
previousX = canvas.px2x(px)
} }
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,19 +16,19 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { parseDomain, executeExpression, Expression, EmptySet, Domain } from "../mathlib.mjs" .pragma library
import * as P from "../parameters.mjs"
import Objects from "../objects.mjs"
import Latex from "../math/latex.mjs"
import { API as Common, ExecutableObject } from "common.mjs" .import "common.js" as Common
import Function from "function.mjs" .import "function.js" as F
.import "../objects.js" as Objects
import { API as HistoryAPI } from "../history/common.mjs" .import "../utils.js" as Utils
import { CreateNewObject } from "../historylib.mjs" .import "../mathlib.js" as MathLib
.import "../historylib.js" as HistoryLib
.import "../parameters.js" as P
.import "../math/latex.js" as Latex
export default class GainBode extends ExecutableObject { class GainBode extends Common.ExecutableObject {
static type(){return 'Gain Bode'} static type(){return 'Gain Bode'}
static displayType(){return qsTr('Bode Magnitude')} static displayType(){return qsTr('Bode Magnitude')}
static displayTypeMultiple(){return qsTr('Bode Magnitudes')} static displayTypeMultiple(){return qsTr('Bode Magnitudes')}
@ -40,11 +40,11 @@ export default class GainBode extends ExecutableObject {
[QT_TRANSLATE_NOOP('prop','labelX')]: 'number', [QT_TRANSLATE_NOOP('prop','labelX')]: 'number',
[QT_TRANSLATE_NOOP('prop','omGraduation')]: 'boolean' [QT_TRANSLATE_NOOP('prop','omGraduation')]: 'boolean'
}} }}
constructor(name = null, visible = true, color = null, labelContent = 'name + value', constructor(name = null, visible = true, color = null, labelContent = 'name + value',
om_0 = '', pass = 'high', gain = '20', labelPosition = 'above', labelX = 1, omGraduation = false) { om_0 = '', pass = 'high', gain = '20', labelPosition = 'above', labelX = 1, omGraduation = false) {
if(name == null) name = Common.getNewName('G') if(name == null) name = Common.getNewName('G')
if(name === 'G') name = 'G₀' // G is reserved for sum of BODE magnitudes (Somme gains Bode). if(name == 'G') name = 'G₀' // G is reserved for sum of BODE magnitudes (Somme gains Bode).
super(name, visible, color, labelContent) super(name, visible, color, labelContent)
if(typeof om_0 == "string") { if(typeof om_0 == "string") {
// Point name or create one // Point name or create one
@ -52,16 +52,15 @@ export default class GainBode extends ExecutableObject {
if(om_0 == null) { if(om_0 == null) {
// Create new point // Create new point
om_0 = Objects.createNewRegisteredObject('Point', [Common.getNewName('ω'), true, this.color, 'name']) om_0 = Objects.createNewRegisteredObject('Point', [Common.getNewName('ω'), true, this.color, 'name'])
HistoryAPI.addToHistory(new CreateNewObject(om_0.name, 'Point', om_0.export())) HistoryLib.history.addToHistory(new HistoryLib.CreateNewObject(om_0.name, 'Point', om_0.export()))
om_0.update() om_0.update()
labelPosition = 'below' labelPosition = 'below'
} }
om_0.requiredBy.push(this) om_0.requiredBy.push(this)
} }
/** @type {Point} */
this.om_0 = om_0 this.om_0 = om_0
this.pass = pass this.pass = pass
if(typeof gain == 'number' || typeof gain == 'string') gain = new Expression(gain.toString()) if(typeof gain == 'number' || typeof gain == 'string') gain = new MathLib.Expression(gain.toString())
this.gain = gain this.gain = gain
this.labelPosition = labelPosition this.labelPosition = labelPosition
this.labelX = labelX this.labelX = labelX
@ -69,22 +68,27 @@ export default class GainBode extends ExecutableObject {
} }
getReadableString() { getReadableString() {
let pass = this.pass === "low" ? qsTr("low-pass") : qsTr("high-pass"); 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` return `${this.name}: ${pass}; ${this.om_0.name} = ${this.om_0.x}\n ${' '.repeat(this.name.length)}${this.gain.toString(true)} dB/dec`
} }
getLatexString() { getLatexString() {
let pass = this.pass === "low" ? qsTr("low-pass") : qsTr("high-pass"); let pass = this.pass == "low" ? qsTr("low-pass") : qsTr("high-pass");
return `\\mathrm{${Latex.variable(this.name)}:}\\begin{array}{l} return `\\mathrm{${Latex.variable(this.name)}:}\\begin{array}{l}
\\textsf{${pass}};${Latex.variable(this.om_0.name)} = ${this.om_0.x.latexMarkup} \\\\ \\textsf{${pass}};${Latex.variable(this.om_0.name)} = ${this.om_0.x.latexMarkup} \\\\
${this.gain.latexMarkup}\\textsf{ dB/dec} ${this.gain.latexMarkup}\\textsf{ dB/dec}
\\end{array}` \\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) { execute(x=1) {
if(typeof x == 'string') x = executeExpression(x) 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)) { if((this.pass == 'high' && x < this.om_0.x) || (this.pass == 'low' && x > this.om_0.x)) {
let dbfn = new Expression(`${this.gain.execute()}*(ln(x)-ln(${this.om_0.x}))/ln(10)+${this.om_0.y}`) 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) return dbfn.execute(x)
} else { } else {
return this.om_0.y.execute() return this.om_0.y.execute()
@ -92,10 +96,10 @@ export default class GainBode extends ExecutableObject {
} }
simplify(x = 1) { simplify(x = 1) {
let xval = x var xval = x
if(typeof x == 'string') xval = executeExpression(x) if(typeof x == 'string') xval = MathLib.executeExpression(x)
if((this.pass === 'high' && xval < this.om_0.x.execute()) || (this.pass === 'low' && xval > this.om_0.x.execute())) { if((this.pass == 'high' && xval < this.om_0.x) || (this.pass == 'low' && xval > this.om_0.x)) {
let dbfn = new Expression(`${this.gain.execute()}*(ln(x)-ln(${this.om_0.x}))/ln(10)+${this.om_0.y}`) 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) return dbfn.simplify(x)
} else { } else {
return this.om_0.y.toString() return this.om_0.y.toString()
@ -106,34 +110,34 @@ export default class GainBode extends ExecutableObject {
return true return true
} }
draw(canvas) { draw(canvas, ctx) {
let base = [canvas.x2px(this.om_0.x.execute()), canvas.y2px(this.om_0.y.execute())] var base = [canvas.x2px(this.om_0.x), canvas.y2px(this.om_0.y)]
let dbfn = new Expression(`${this.gain.execute()}*(log10(x)-log10(${this.om_0.x}))+${this.om_0.y}`) var dbfn = new MathLib.Expression(`${this.gain.execute()}*(ln(x)-ln(${this.om_0.x}))/ln(10)+${this.om_0.y}`)
let inDrawDom = new EmptySet() var inDrawDom = new MathLib.EmptySet()
if(this.pass === 'high') { if(this.pass == 'high') {
// High pass, linear line from beginning, then constant to the end. // High pass, linear line from begining, then constant to the end.
canvas.drawLine(base[0], base[1], canvas.width, base[1]) canvas.drawLine(ctx, base[0], base[1], canvas.canvasSize.width, base[1])
inDrawDom = parseDomain(`]-inf;${this.om_0.x}[`) inDrawDom = MathLib.parseDomain(`]-inf;${this.om_0.x}[`)
} else { } else {
// Low pass, constant from the beginning, linear line to the end. // Low pass, constant from the beginning, linear line to the end.
canvas.drawLine(base[0], base[1], 0, base[1]) canvas.drawLine(ctx, base[0], base[1], 0, base[1])
inDrawDom = parseDomain(`]${this.om_0.x};+inf[`) inDrawDom = MathLib.parseDomain(`]${this.om_0.x};+inf[`)
} }
Function.drawFunction(canvas, dbfn, inDrawDom, Domain.R) F.Function.drawFunction(canvas, ctx, dbfn, inDrawDom, MathLib.Domain.R)
// Dashed line representing break in function // Dashed line representing break in function
let xpos = canvas.x2px(this.om_0.x.execute()) var xpos = canvas.x2px(this.om_0.x.execute())
let dashPxSize = 10 var dashPxSize = 10
for(let i = 0; i < canvas.height && this.omGraduation; i += dashPxSize*2) for(var i = 0; i < canvas.canvasSize.height && this.omGraduation; i += dashPxSize*2)
canvas.drawLine(xpos, i, xpos, i+dashPxSize) canvas.drawLine(ctx, xpos, i, xpos, i+dashPxSize)
// Label // Label
this.drawLabel(canvas, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX))) this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))
} }
update() { update() {
super.update() super.update()
if(Objects.currentObjects['Somme gains Bode'] !== undefined && Objects.currentObjects['Somme gains Bode'].length > 0) { if(Objects.currentObjects['Somme gains Bode'] != undefined && Objects.currentObjects['Somme gains Bode'].length > 0) {
Objects.currentObjects['Somme gains Bode'][0].recalculateCache() Objects.currentObjects['Somme gains Bode'][0].recalculateCache()
} else { } else {
Objects.createNewRegisteredObject('Somme gains Bode') Objects.createNewRegisteredObject('Somme gains Bode')

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,17 +16,17 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { executeExpression, Expression } from "../mathlib.mjs" .pragma library
import * as P from "../parameters.mjs"
import Objects from "../objects.mjs"
import Latex from "../math/latex.mjs"
import { API as Common, ExecutableObject } from "common.mjs" .import "common.js" as Common
import { API as HistoryAPI } from "../history/common.mjs" .import "../objects.js" as Objects
import { CreateNewObject } from "../historylib.mjs" .import "../mathlib.js" as MathLib
.import "../historylib.js" as HistoryLib
.import "../parameters.js" as P
.import "../math/latex.js" as Latex
export default class PhaseBode extends ExecutableObject { class PhaseBode extends Common.ExecutableObject {
static type(){return 'Phase Bode'} static type(){return 'Phase Bode'}
static displayType(){return qsTr('Bode Phase')} static displayType(){return qsTr('Bode Phase')}
static displayTypeMultiple(){return qsTr('Bode Phases')} static displayTypeMultiple(){return qsTr('Bode Phases')}
@ -41,9 +41,9 @@ export default class PhaseBode extends ExecutableObject {
constructor(name = null, visible = true, color = null, labelContent = 'name + value', constructor(name = null, visible = true, color = null, labelContent = 'name + value',
om_0 = '', phase = 90, unit = '°', labelPosition = 'above', labelX = 1) { om_0 = '', phase = 90, unit = '°', labelPosition = 'above', labelX = 1) {
if(name == null) name = Common.getNewName('φ') if(name == null) name = Common.getNewName('φ')
if(name === 'φ') name = 'φ₀' // φ is reserved for sum of BODE phases (Somme phases Bode). if(name == 'φ') name = 'φ₀' // φ is reserved for sum of BODE phases (Somme phases Bode).
super(name, visible, color, labelContent) super(name, visible, color, labelContent)
if(typeof phase == 'number' || typeof phase == 'string') phase = new Expression(phase.toString()) if(typeof phase == 'number' || typeof phase == 'string') phase = new MathLib.Expression(phase.toString())
this.phase = phase this.phase = phase
if(typeof om_0 == "string") { if(typeof om_0 == "string") {
// Point name or create one // Point name or create one
@ -52,18 +52,22 @@ export default class PhaseBode extends ExecutableObject {
// Create new point // Create new point
om_0 = Objects.createNewRegisteredObject('Point', [Common.getNewName('ω'), this.color, 'name']) om_0 = Objects.createNewRegisteredObject('Point', [Common.getNewName('ω'), this.color, 'name'])
om_0.labelPosition = this.phase.execute() >= 0 ? 'above' : 'below' om_0.labelPosition = this.phase.execute() >= 0 ? 'above' : 'below'
HistoryAPI.history.addToHistory(new CreateNewObject(om_0.name, 'Point', om_0.export())) HistoryLib.history.addToHistory(new HistoryLib.CreateNewObject(om_0.name, 'Point', om_0.export()))
labelPosition = 'below' labelPosition = 'below'
} }
om_0.requiredBy.push(this) om_0.requiredBy.push(this)
} }
/** @type {Point} */
this.om_0 = om_0 this.om_0 = om_0
this.unit = unit this.unit = unit
this.labelPosition = labelPosition this.labelPosition = labelPosition
this.labelX = labelX 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() { getReadableString() {
return `${this.name}: ${this.phase.toString(true)}${this.unit} at ${this.om_0.name} = ${this.om_0.x}` return `${this.name}: ${this.phase.toString(true)}${this.unit} at ${this.om_0.name} = ${this.om_0.x}`
} }
@ -73,7 +77,7 @@ export default class PhaseBode extends ExecutableObject {
} }
execute(x=1) { execute(x=1) {
if(typeof x == 'string') x = executeExpression(x) if(typeof x == 'string') x = MathLib.executeExpression(x)
if(x < this.om_0.x) { if(x < this.om_0.x) {
return this.om_0.y.execute() return this.om_0.y.execute()
} else { } else {
@ -82,13 +86,13 @@ export default class PhaseBode extends ExecutableObject {
} }
simplify(x = 1) { simplify(x = 1) {
let xval = x var xval = x
if(typeof x == 'string') xval = executeExpression(x) if(typeof x == 'string') xval = MathLib.executeExpression(x)
if(xval < this.om_0.x) { if(xval < this.om_0.x) {
return this.om_0.y.toString() return this.om_0.y.toString()
} else { } else {
let newExp = this.om_0.y.toEditableString() + ' + ' + this.phase.toEditableString() var newExp = this.om_0.y.toEditableString() + ' + ' + this.phase.toEditableString()
return (new Expression(newExp)).toString() return (new MathLib.Expression(newExp)).toString()
} }
} }
@ -96,25 +100,25 @@ export default class PhaseBode extends ExecutableObject {
return true return true
} }
draw(canvas) { draw(canvas, ctx) {
let baseX = canvas.x2px(this.om_0.x.execute()) var baseX = canvas.x2px(this.om_0.x.execute())
let omy = this.om_0.y.execute() var omy = this.om_0.y.execute()
let augmt = this.phase.execute() var augmt = this.phase.execute()
let baseY = canvas.y2px(omy) var baseY = canvas.y2px(omy)
let augmtY = canvas.y2px(omy+augmt) var augmtY = canvas.y2px(omy+augmt)
// Before change line. // Before change line.
canvas.drawLine(0, baseY, Math.min(baseX, canvas.height), baseY) canvas.drawLine(ctx, 0, baseY, Math.min(baseX, canvas.canvasSize.height), baseY)
// Transition line. // Transition line.
canvas.drawLine(baseX, baseY, baseX, augmtY) canvas.drawLine(ctx, baseX, baseY, baseX, augmtY)
// After change line // After change line
canvas.drawLine(Math.max(0, baseX), augmtY, canvas.width, augmtY) canvas.drawLine(ctx, Math.max(0, baseX), augmtY, canvas.canvasSize.width, augmtY)
// Label // Label
this.drawLabel(canvas, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX))) this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))
} }
update() { update() {
if(Objects.currentObjects['Somme phases Bode'] !== undefined && Objects.currentObjects['Somme phases Bode'].length > 0) { if(Objects.currentObjects['Somme phases Bode'] != undefined && Objects.currentObjects['Somme phases Bode'].length > 0) {
Objects.currentObjects['Somme phases Bode'][0].recalculateCache() Objects.currentObjects['Somme phases Bode'][0].recalculateCache()
} else { } else {
Objects.createNewRegisteredObject('Somme phases Bode') Objects.createNewRegisteredObject('Somme phases Bode')

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,13 +16,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Expression } from "../mathlib.mjs" .pragma library
import * as P from "../parameters.mjs"
import Latex from "../math/latex.mjs" .import "common.js" as Common
import { API as Common, DrawableObject } from "common.mjs" .import "../mathlib.js" as MathLib
.import "../parameters.js" as P
.import "../math/latex.js" as Latex
export default class Point extends DrawableObject { class Point extends Common.DrawableObject {
static type(){return 'Point'} static type(){return 'Point'}
static displayType(){return qsTr('Point')} static displayType(){return qsTr('Point')}
static displayTypeMultiple(){return qsTr('Points')} static displayTypeMultiple(){return qsTr('Points')}
@ -38,9 +40,9 @@ export default class Point extends DrawableObject {
x = 1, y = 0, labelPosition = 'above', pointStyle = '●') { x = 1, y = 0, labelPosition = 'above', pointStyle = '●') {
if(name == null) name = Common.getNewName('ABCDEFJKLMNOPQRSTUVW') if(name == null) name = Common.getNewName('ABCDEFJKLMNOPQRSTUVW')
super(name, visible, color, labelContent) super(name, visible, color, labelContent)
if(typeof x == 'number' || typeof x == 'string') x = new Expression(x.toString()) if(typeof x == 'number' || typeof x == 'string') x = new MathLib.Expression(x.toString())
this.x = x this.x = x
if(typeof y == 'number' || typeof y == 'string') y = new Expression(y.toString()) if(typeof y == 'number' || typeof y == 'string') y = new MathLib.Expression(y.toString())
this.y = y this.y = y
this.labelPosition = labelPosition this.labelPosition = labelPosition
this.pointStyle = pointStyle this.pointStyle = pointStyle
@ -54,22 +56,28 @@ export default class Point extends DrawableObject {
return `${Latex.variable(this.name)} = \\left(${this.x.latexMarkup}, ${this.y.latexMarkup}\\right)` return `${Latex.variable(this.name)} = \\left(${this.x.latexMarkup}, ${this.y.latexMarkup}\\right)`
} }
draw(canvas) { export() {
let [canvasX, canvasY] = [canvas.x2px(this.x.execute()), canvas.y2px(this.y.execute())] return [this.name, this.visible, this.color.toString(), this.labelContent, this.x.toEditableString(), this.y.toEditableString(), this.labelPosition, this.pointStyle]
let pointSize = 8+(canvas.linewidth*2) }
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) { switch(this.pointStyle) {
case '●': case '●':
canvas.disc(canvasX, canvasY, pointSize/2) ctx.beginPath();
ctx.ellipse(canvasX-pointSize/2, canvasY-pointSize/2, pointSize, pointSize)
ctx.fill();
break; break;
case '✕': case '✕':
canvas.drawLine(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)
canvas.drawLine(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; break;
case '': case '':
canvas.fillRect(canvasX-pointSize/2, canvasY-1, pointSize, 2) ctx.fillRect(canvasX-pointSize/2, canvasY-1, pointSize, 2)
canvas.fillRect(canvasX-1, canvasY-pointSize/2, 2, pointSize) ctx.fillRect(canvasX-1, canvasY-pointSize/2, 2, pointSize)
break; break;
} }
this.drawLabel(canvas, this.labelPosition, canvasX, canvasY) this.drawLabel(canvas, ctx, this.labelPosition, canvasX, canvasY)
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,44 +16,45 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { API as Common, ExecutableObject } from "common.mjs" .pragma library
import * as P from "../parameters.mjs"
import Latex from "../math/latex.mjs" .import "common.js" as Common
.import "../parameters.js" as P
.import "../math/latex.js" as Latex
export default class RepartitionFunction extends ExecutableObject { class RepartitionFunction extends Common.ExecutableObject {
static type(){return 'Repartition'} static type(){return 'Repartition'}
static displayType(){return qsTr('Repartition')} static displayType(){return qsTr('Repartition')}
static displayTypeMultiple(){return qsTr('Repartition functions')} static displayTypeMultiple(){return qsTr('Repartition functions')}
static properties() {return { static properties() {return {
[QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position,
[QT_TRANSLATE_NOOP('prop','labelX')]: 'number',
'comment1': QT_TRANSLATE_NOOP( 'comment1': QT_TRANSLATE_NOOP(
'comment', 'comment',
'Note: Specify the probability for each value.' 'Note: Specify the probability for each value.'
), ),
[QT_TRANSLATE_NOOP('prop','probabilities')]: new P.Dictionary('string', 'double', /^-?[\d.,]+$/, /^-?[\d.,]+$/, 'P({name_} = ', ') = '), [QT_TRANSLATE_NOOP('prop','probabilities')]: new P.Dictionary('string', 'float', /^-?[\d.,]+$/, /^-?[\d\.,]+$/, 'P({name_} = ', ') = '),
[QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position,
[QT_TRANSLATE_NOOP('prop','labelX')]: 'number',
}} }}
static import(name, visible, color, labelContent, ...args) {
console.log(args, args.length)
if(args.length === 5) {
// Two legacy values no longer included.
args.shift()
args.shift()
}
return super.import(name, visible, color, labelContent, ...args)
}
constructor(name = null, visible = true, color = null, labelContent = 'name + value', constructor(name = null, visible = true, color = null, labelContent = 'name + value',
probabilities = {'0': '0'}, labelPosition = 'above', labelX = 1) { beginIncluded = true, drawLineEnds = true, probabilities = {'0': '0'}, labelPosition = 'above', labelX = 1) {
if(name == null) name = Common.getNewName('XYZUVW', "F_") if(name == null) name = Common.getNewName('XYZUVW', "F_")
super(name, visible, color, labelContent) super(name, visible, color, labelContent)
this.beginIncluded = beginIncluded
this.drawLineEnds = drawLineEnds
this.probabilities = probabilities this.probabilities = probabilities
this.labelPosition = labelPosition this.labelPosition = labelPosition
this.labelX = labelX this.labelX = labelX
this.update() this.update()
} }
export() {
return [this.name, this.visible, this.color.toString(), this.labelContent,
this.beginIncluded, this.drawLineEnds, this.probabilities, this.labelPosition, this.labelX]
}
getReadableString() { getReadableString() {
let keys = Object.keys(this.probabilities).sort((a,b) => a-b); let keys = Object.keys(this.probabilities).sort((a,b) => a-b);
let varname = this.name.substring(this.name.indexOf("_")+1) let varname = this.name.substring(this.name.indexOf("_")+1)
@ -68,7 +69,7 @@ export default class RepartitionFunction extends ExecutableObject {
} }
execute(x = 1) { execute(x = 1) {
let ret = 0; var ret = 0;
Object.keys(this.probabilities).sort((a,b) => a-b).forEach(idx => { Object.keys(this.probabilities).sort((a,b) => a-b).forEach(idx => {
if(x >= idx) ret += parseFloat(this.probabilities[idx].replace(/,/g, '.')) if(x >= idx) ret += parseFloat(this.probabilities[idx].replace(/,/g, '.'))
}) })
@ -76,7 +77,6 @@ export default class RepartitionFunction extends ExecutableObject {
} }
canExecute(x = 1) {return true} canExecute(x = 1) {return true}
// Simplify returns the simplified string of the expression. // Simplify returns the simplified string of the expression.
simplify(x = 1) { simplify(x = 1) {
return this.execute(x) return this.execute(x)
@ -93,51 +93,61 @@ export default class RepartitionFunction extends ExecutableObject {
} }
} }
draw(canvas) { draw(canvas, ctx) {
let currentY = 0; var currentY = 0;
let keys = Object.keys(this.probabilities).map(idx => parseInt(idx)).sort((a,b) => a-b) 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, '.'))) { if(canvas.visible(keys[0],this.probabilities[keys[0]].replace(/,/g, '.'))) {
canvas.drawLine(0, canvas.y2px(0), canvas.x2px(keys[0]), canvas.y2px(0)) canvas.drawLine(ctx,
if(canvas.isVisible(keys[0],0)) { 0,
canvas.arc(canvas.x2px(keys[0])+4,canvas.y2px(0), 4, Math.PI / 2, 3 * Math.PI / 2); canvas.y2px(0),
canvas.x2px(keys[0]),
canvas.y2px(0)
)
if(canvas.visible(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(let i = 0; i < keys.length-1; i++) { for(var i = 0; i < keys.length-1; i++) {
let idx = keys[i]; var idx = keys[i];
currentY += parseFloat(this.probabilities[idx].replace(/,/g, '.')); currentY += parseFloat(this.probabilities[idx].replace(/,/g, '.'));
if(canvas.isVisible(idx,currentY) || canvas.isVisible(keys[i+1],currentY)) { if(canvas.visible(idx,currentY) || canvas.visible(keys[i+1],currentY)) {
canvas.drawLine( canvas.drawLine(ctx,
Math.max(0,canvas.x2px(idx)), Math.max(0,canvas.x2px(idx)),
canvas.y2px(currentY), canvas.y2px(currentY),
Math.min(canvas.width,canvas.x2px(keys[i+1])), Math.min(canvas.canvasSize.width,canvas.x2px(keys[i+1])),
canvas.y2px(currentY) canvas.y2px(currentY)
) )
if(canvas.isVisible(idx,currentY)) { if(canvas.visible(idx,currentY)) {
canvas.disc(canvas.x2px(idx), canvas.y2px(currentY), 4) ctx.beginPath();
ctx.arc(canvas.x2px(idx),canvas.y2px(currentY), 4, 0, 2 * Math.PI);
ctx.fill();
} }
if(canvas.isVisible(keys[i+1],currentY)) { if(canvas.visible(keys[i+1],currentY)) {
canvas.arc(canvas.x2px(keys[i+1])+4,canvas.y2px(currentY), 4, Math.PI / 2, 3 * Math.PI / 2); 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]]))) { if(canvas.visible(keys[keys.length-1],currentY+parseFloat(this.probabilities[keys[keys.length-1]]))) {
canvas.drawLine( canvas.drawLine(ctx,
Math.max(0,canvas.x2px(keys[keys.length-1])), Math.max(0,canvas.x2px(keys[keys.length-1])),
canvas.y2px(currentY+parseFloat(this.probabilities[keys[keys.length-1]].replace(/,/g, '.'))), canvas.y2px(currentY+parseFloat(this.probabilities[keys[keys.length-1]].replace(/,/g, '.'))),
canvas.width, canvas.canvasSize.width,
canvas.y2px(currentY+parseFloat(this.probabilities[keys[keys.length-1]].replace(/,/g, '.'))) canvas.y2px(currentY+parseFloat(this.probabilities[keys[keys.length-1]].replace(/,/g, '.')))
) )
canvas.disc( ctx.beginPath();
ctx.arc(
canvas.x2px(keys[keys.length-1]), canvas.x2px(keys[keys.length-1]),
canvas.y2px( canvas.y2px(currentY+parseFloat(this.probabilities[keys[keys.length-1]].replace(/,/g, '.'))),
currentY+parseFloat(this.probabilities[keys[keys.length-1]].replace(/,/g, '.')) 4, 0, 2 * Math.PI);
), ctx.fill();
4
)
} }
// Label // Label
this.drawLabel(canvas, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX))) this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2022 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,15 +16,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Sequence as MathSequence, Domain } from "../mathlib.mjs" .pragma library
import * as P from "../parameters.mjs"
import Latex from "../math/latex.mjs"
import { API as Common, ExecutableObject } from "common.mjs" .import "common.js" as Common
import Function from "function.mjs" .import "function.js" as F
.import "../mathlib.js" as MathLib
.import "../parameters.js" as P
.import "../math/latex.js" as Latex
export default class Sequence extends ExecutableObject { class Sequence extends Common.ExecutableObject {
static type(){return 'Sequence'} static type(){return 'Sequence'}
static displayType(){return qsTr('Sequence')} static displayType(){return qsTr('Sequence')}
static displayTypeMultiple(){return qsTr('Sequences')} static displayTypeMultiple(){return qsTr('Sequences')}
@ -55,17 +56,21 @@ export default class Sequence extends ExecutableObject {
this.labelX = labelX this.labelX = labelX
this.update() 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() { update() {
console.log('Updating sequence', this.sequence)
super.update() super.update()
if( if(
this.sequence == null || this.baseValues !== this.sequence.baseValues || this.sequence == null || this.baseValues != this.sequence.baseValues ||
this.sequence.name !== this.name || this.sequence.name != this.name ||
this.sequence.expr !== Object.values(this.defaultExpression)[0] || this.sequence.expr != Object.values(this.defaultExpression)[0] ||
this.sequence.valuePlus !== Object.keys(this.defaultExpression)[0] this.sequence.valuePlus != Object.keys(this.defaultExpression)[0]
) )
this.sequence = new MathSequence( this.sequence = new MathLib.Sequence(
this.name, this.baseValues, this.name, this.baseValues,
Object.keys(this.defaultExpression)[0], Object.keys(this.defaultExpression)[0],
Object.values(this.defaultExpression)[0] Object.values(this.defaultExpression)[0]
@ -81,15 +86,15 @@ export default class Sequence extends ExecutableObject {
} }
execute(x = 1) { execute(x = 1) {
if(x % 1 === 0) if(x % 1 == 0)
return this.sequence.execute(x) return this.sequence.execute(x)
return null return null
} }
canExecute(x = 1) {return x%1 === 0} canExecute(x = 1) {return x%1 == 0}
// Simplify returns the simplified string of the expression. // Simplify returns the simplified string of the expression.
simplify(x = 1) { simplify(x = 1) {
if(x % 1 === 0) if(x % 1 == 0)
return this.sequence.simplify(x) return this.sequence.simplify(x)
return null return null
} }
@ -116,11 +121,11 @@ export default class Sequence extends ExecutableObject {
} }
} }
draw(canvas) { draw(canvas, ctx) {
Function.drawFunction(canvas, this.sequence, canvas.logscalex ? Domain.NE : Domain.N, Domain.R, this.drawPoints, this.drawDashedLines) F.Function.drawFunction(canvas, ctx, this.sequence, canvas.logscalex ? MathLib.Domain.NE : MathLib.Domain.N, MathLib.Domain.R, this.drawPoints, this.drawDashedLines)
// Label // Label
this.drawLabel(canvas, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX))) this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))
} }
} }

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