Compare commits

...

63 commits

Author SHA1 Message Date
ovari 136ac4c7bc
Translated using Weblate (Hungarian)
All checks were successful
continuous-integration/drone/push Build is passing
Currently translated at 100.0% (304 of 304 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/hu/
2024-04-21 08:07:05 +02:00
gallegonovato 87d8882db1
Translated using Weblate (Spanish)
All checks were successful
continuous-integration/drone/push Build is passing
Currently translated at 80.2% (244 of 304 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/es/
2024-04-03 23:01:47 +02:00
Ad5001 915c6b5246
Setting version to v0.6.0
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-03 22:05:57 +02:00
Ad5001 54f9802975
Adding default canvas settings
All checks were successful
continuous-integration/drone/push Build is passing
+ Getting rid of Qt5COmpat for Icon.
+ Fixing some (non production) bugs.
2024-04-03 21:39:06 +02:00
Ad5001 997a1645a0
Removing Settings submenu.
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-02 22:50:43 +02:00
Ad5001 fefb0f92b0
New, rewamped Greet Screen. 2024-04-02 22:49:56 +02:00
Ad5001 665906ecb3
New preferences panel. 2024-04-02 22:11:54 +02:00
Ad5001 781a0f4aae
Enforcing type safety at export
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-02 00:11:56 +02:00
Ad5001 5f8c756dc7
Enforcing type safety at import. 2024-04-01 23:48:57 +02:00
Ad5001 8e75f13e9a
Removing vanished translations.
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-29 17:56:09 +01:00
Ad5001 82e8413e56
Creating IO JS module.
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-29 17:53:10 +01:00
Ad5001 73cba85592
Creating Canvas JS Module.
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-29 01:55:13 +01:00
Ad5001 861bb001c9
Changing version, making Helper objects globals, adding their polyfills.
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-28 22:52:14 +01:00
Ad5001 08fea34366
Converting as many JS libraries to ECMAScript modules.
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-28 03:09:37 +01:00
Ad5001 a6fcf6da19
Fixing sequence no longer working with current rendering method.
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-25 18:58:07 +01:00
Ad5001 f730121047
Fixing the icon on wayland, building on MacOS, and date on changelog
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-06 18:52:58 +01:00
Ad5001 80f1077b35
Updating snapcraft
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-11 23:28:42 +01:00
Ad5001 33c790b113
Updating debian information
Some checks reported errors
continuous-integration/drone/push Build encountered an error
continuous-integration/drone Build is failing
2024-01-11 21:20:23 +01:00
Ad5001 f9b0bb99ce
Updating appstream
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2024-01-11 20:15:25 +01:00
Ad5001 557592708f
Fixing building for debian 2024-01-11 19:56:25 +01:00
Ad5001 1f72c76fb4
Releasing v0.5.0
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2024-01-11 19:24:06 +01:00
Ad5001 66b4c8df90
Merge branch 'master' of https://git.ad5001.eu/Ad5001/LogarithmPlotter
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2024-01-11 02:13:44 +01:00
Ad5001 70b1ac09f7
Translated using Weblate (German)
All checks were successful
continuous-integration/drone/push Build is passing
Currently translated at 100.0% (271 of 271 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/de/
2024-01-11 02:13:33 +01:00
Ad5001 2d6389bfd5
Updating images for CI 2024-01-11 01:49:29 +01:00
Ad5001 d0851b819f
Updating windows installer
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-11 01:15:43 +01:00
Ad5001 cff994d9d7
Fixing macos building
Some checks failed
continuous-integration/drone/push Build is failing
2024-01-11 00:59:01 +01:00
Ad5001 fde2526c54
Updating copyrights
Some checks failed
continuous-integration/drone/push Build is failing
2024-01-11 00:11:09 +01:00
Ad5001 e5fe8afd06
Fixing issue with derivative and integrals
Some checks failed
continuous-integration/drone/push Build is failing
2024-01-11 00:09:35 +01:00
Ad5001 f9a7443631
New icons for macos and windows
Some checks failed
continuous-integration/drone/push Build is failing
2024-01-10 23:42:17 +01:00
Ad5001 874046960f
Changing linux descriptions
Some checks failed
continuous-integration/drone/push Build is failing
2024-01-10 23:32:47 +01:00
Ad5001 d53573d468
Merge branch 'master' of https://git.ad5001.eu/Ad5001/LogarithmPlotter
Some checks failed
continuous-integration/drone/push Build is failing
2024-01-10 22:57:33 +01:00
Ad5001 1da1221c51
Updating icons 2024-01-10 22:57:18 +01:00
ovari 8bb147cbee
Translated using Weblate (Hungarian)
Some checks failed
continuous-integration/drone/push Build is failing
Currently translated at 100.0% (271 of 271 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/hu/
2023-10-20 04:11:48 +00:00
Ad5001 bd2e692285
Merge branch 'master' of https://git.ad5001.eu/Ad5001/LogarithmPlotter
Some checks failed
continuous-integration/drone/push Build is failing
2023-10-18 16:06:26 +02:00
Ad5001 298acaace2
Reworking SVG object icons to use default system sans-serif font.
Provides better native integration at the cost of uniformisation.
2023-10-18 16:06:11 +02:00
Ad5001 5284c71ee7
Translated using Weblate (French)
Some checks failed
continuous-integration/drone/push Build is failing
Currently translated at 100.0% (271 of 271 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/fr/
2023-10-11 15:01:31 +00:00
Ad5001 b8d679e118
Translated using Weblate (English)
Currently translated at 100.0% (271 of 271 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/en/
2023-10-11 15:01:30 +00:00
Ad5001 22106f97b5
Adding new strings
Some checks failed
continuous-integration/drone/push Build is failing
2023-10-10 02:13:17 +02:00
Ad5001 bf211df67d
Adding function usage to auto complete.
Some checks failed
continuous-integration/drone/push Build is failing
2023-10-10 01:58:55 +02:00
Ad5001 803416d08d
New system for integrals the same as for derivatives.
Some checks failed
continuous-integration/drone/push Build is failing
2023-10-10 01:33:57 +02:00
Ad5001 d991deee7b
Reworking the derivative function, removed assignement in parser.
The new derivative now supports executable elements.
2023-10-10 00:53:35 +02:00
Ad5001 ed4d30573c
Adding usage for functions. 2023-10-10 00:05:19 +02:00
Ad5001 3f1d089a78
Minor modifications, adding usage to derivative. 2023-10-09 23:28:29 +02:00
Ad5001 4b692894f2
Removing old and unused bits for previously planned new parser, that would have remained unused.
Some checks failed
continuous-integration/drone/push Build is failing
Some bits (like the tokenizer and the reference) are still used for the syntax highlighting and autocomplete though.
2023-10-09 23:27:30 +02:00
Ad5001 062fde6b16
Updating build script for newest pyinstaller version. 2023-10-09 22:26:50 +02:00
Ad5001 2b5d442d3a
Changing v0.4.1 to v0.5.0 considering the number of changes.
Some checks failed
continuous-integration/drone/push Build is failing
2023-10-09 20:29:21 +02:00
Ad5001 038dd9f4a8
Fixing the (previously very broken) text renderer when using symbols.
Some checks failed
continuous-integration/drone/push Build is failing
2023-10-09 18:50:34 +02:00
Ad5001 cf754a7a34
Better handling of constants in simplified expressions. 2023-10-09 18:37:28 +02:00
Ad5001 bc35b18da0
Insert character popup's characters are now contextually aware.
Some checks failed
continuous-integration/drone/push Build is failing
2023-10-09 17:55:06 +02:00
Ad5001 7542d63121
Adding new characters to insert chars popup. 2023-10-09 17:20:32 +02:00
Ad5001 4fe1086d68
Adding new behavior for object creation to default on picker when pickable.
Some checks failed
continuous-integration/drone/push Build is failing
2023-10-08 22:36:23 +02:00
Ad5001 a66ccd1319
Changing version to v0.4.1
Some checks failed
continuous-integration/drone/push Build is failing
2023-10-08 18:34:51 +02:00
Ad5001 9879e7fbc9
Fixing changelog 2023-10-08 18:34:09 +02:00
Ad5001 999999832a
Taking edge cases into account into new drawing system.
Some checks failed
continuous-integration/drone/push Build is failing
2023-10-08 17:22:21 +02:00
Ad5001 7408520819
Reworking continuous functions' rendering to make it faster.
Some checks failed
continuous-integration/drone/push Build is failing
+ Diminishing pixel precision from 1 to 10.
+ The change is barely noticable by the human eye in most functions.
+ Reworked domain checking to keep the pixel perfect precision.

The only visual degradations are functions with very high y variations for very small x variations (like cos and sin on log scale).
2023-10-08 17:00:12 +02:00
Ad5001 9f2b08b938
Fixing "Unknown variable ∞." message.
It occured when using a domain with the infinity symbol.
2023-10-08 16:31:01 +02:00
Ad5001 3039aade29
Fixing some issues with infinity in domains. 2023-10-08 16:24:16 +02:00
Ad5001 d6a83b0f4b
Adding the ability to scroll to zoom.
Some checks failed
continuous-integration/drone/push Build is failing
2023-10-07 23:38:51 +02:00
Ad5001 0b5aa36c23
Adding view position changer overlay. 2023-10-07 19:11:19 +02:00
Ad5001 25347a8eff
Merge branch 'master' of https://git.ad5001.eu/Ad5001/LogarithmPlotter
Some checks failed
continuous-integration/drone/push Build is failing
2023-10-07 02:54:36 +02:00
ovari d6d7f3a511
Translated using Weblate (Hungarian)
All checks were successful
continuous-integration/drone/push Build is passing
Currently translated at 100.0% (267 of 267 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/hu/
2023-06-11 06:48:17 +02:00
Ad5001 e67619771b
Fixing macOS building script. 2023-05-27 11:09:13 +02:00
Ad5001 df3e7d4796
I thought I pushed the updated script for macOS, but it looks like I didn't.
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-27 10:36:37 +02:00
143 changed files with 7918 additions and 8003 deletions

1
.gitignore vendored
View file

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

View file

@ -1,5 +1,35 @@
# 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**

View file

@ -1,6 +1,6 @@
"""
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -17,8 +17,8 @@
"""
from shutil import which
__VERSION__ = "0.4.0"
is_release = True
__VERSION__ = "0.6.0"
is_release = False
# Check if development version, if so get the date of the latest git patch

View file

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

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
lupdate -extensions js,qs,qml,py -recursive .. -ts lp_*.ts
lupdate -extensions mjs,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.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -41,15 +41,16 @@ chdir(path.dirname(path.realpath(__file__)))
if path.realpath(path.join(getcwd(), "..")) not in sys_path:
sys_path.append(path.realpath(path.join(getcwd(), "..")))
from LogarithmPlotter import __VERSION__
from LogarithmPlotter.util import config, native
from LogarithmPlotter.util.update import check_for_updates
from LogarithmPlotter.util.helper import Helper
from LogarithmPlotter.util.latex import Latex
from LogarithmPlotter.util.js import PyJSValue
config.init()
def get_linux_theme():
des = {
"KDE": "Fusion",
@ -62,9 +63,9 @@ def get_linux_theme():
else:
# Android
return "Material"
def run():
if not 'QT_QUICK_CONTROLS_STYLE' in environ:
environ["QT_QUICK_CONTROLS_STYLE"] = {
"linux": get_linux_theme(),
@ -73,9 +74,9 @@ def run():
"cygwin": "Fusion",
"darwin": "macOS"
}[platform]
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();
base_icon_path = path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "icons")
@ -85,42 +86,45 @@ def run():
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "settings")))
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "settings", "custom")))
QIcon.setFallbackSearchPaths(icon_fallbacks);
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
app = QApplication(argv)
app.setApplicationName("LogarithmPlotter")
app.setDesktopFileName("eu.ad5001.LogarithmPlotter.desktop")
app.setOrganizationName("Ad5001")
app.styleHints().setShowShortcutsInContextMenus(True)
app.setWindowIcon(QIcon(path.realpath(path.join(getcwd(), "logarithmplotter.svg"))))
# Installing translators
translator = QTranslator()
# Check if lang is forced.
forcedlang = [p for p in argv if p[:7]=="--lang="]
forcedlang = [p for p in argv if p[:7] == "--lang="]
locale = QLocale(forcedlang[0][7:]) if len(forcedlang) > 0 else QLocale()
if (translator.load(locale, "lp", "_", path.realpath(path.join(getcwd(), "i18n")))):
if translator.load(locale, "lp", "_", path.realpath(path.join(getcwd(), "i18n"))):
app.installTranslator(translator);
# Installing macOS file handler.
macOSFileOpenHandler = None
if platform == "darwin":
macOSFileOpenHandler = native.MacOSFileOpenHandler()
app.installEventFilter(macOSFileOpenHandler)
engine = QQmlApplicationEngine()
global tmpfile
helper = Helper(pwd, tmpfile)
latex = Latex(tempdir)
engine.rootContext().setContextProperty("Helper", helper)
engine.rootContext().setContextProperty("Latex", latex)
js_globals = PyJSValue(engine.globalObject())
js_globals.Modules = engine.newObject()
js_globals.Helper = engine.newQObject(helper)
js_globals.Latex = engine.newQObject(latex)
engine.rootContext().setContextProperty("TestBuild", "--test-build" in argv)
engine.rootContext().setContextProperty("StartTime", dep_time)
app.translate("About","About LogarithmPlotter") # FOR SOME REASON, if this isn't included, Qt refuses to load the QML file.
app.translate("About", "About LogarithmPlotter")
# FOR SOME REASON, if this isn't included, Qt refuses to load the QML file.
engine.addImportPath(path.realpath(path.join(getcwd(), "qml")))
engine.load(path.realpath(path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "LogarithmPlotter.qml")))
if not engine.rootObjects():
print("No root object", path.realpath(path.join(getcwd(), "qml")))
@ -130,26 +134,26 @@ def run():
# Open the current diagram
chdir(pwd)
if len(argv) > 0 and path.exists(argv[-1]) and argv[-1].split('.')[-1] in ['lpf']:
engine.rootObjects()[0].loadDiagram(argv[-1])
js_globals.Modules.IO.loadDiagram(argv[-1])
chdir(path.dirname(path.realpath(__file__)))
if platform == "darwin":
macOSFileOpenHandler.init_graphics(engine.rootObjects()[0])
macOSFileOpenHandler.init_io(js_globals.Modules.IO)
# Check for LaTeX installation if LaTeX support is enabled
if config.getSetting("enable_latex"):
latex.check_latex_install()
# Check for updates
if config.getSetting("check_for_updates"):
check_for_updates(__VERSION__, engine.rootObjects()[0])
exit_code = app.exec_()
exit_code = app.exec()
tempdir.cleanup()
config.save()
exit(exit_code)
if __name__ == "__main__":
run()

View file

@ -1,9 +1,64 @@
<svg id="SVGRoot" width="48" height="48" version="1.1" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<title>LogarithmPlotter Icon v1.0</title>
<g fill-rule="evenodd" stroke-width="2">
<rect width="48" height="48" ry="6" fill="#fff"/>
<rect x="2" y="38" width="44" height="4" stroke-opacity="0"/>
<rect x="18" y="2" width="4" height="44" stroke-opacity="0"/>
</g>
<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"/>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="24.0px"
height="24.0px"
viewBox="0 0 24.0 24.0"
version="1.1"
id="SVGRoot"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><title
id="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: 488 B

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -1,6 +1,6 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -20,9 +20,7 @@ import QtQuick
import Qt.labs.platform as Native
//import QtQuick.Controls 2.15
import eu.ad5001.MixedMenu 1.1
import "js/objects.js" as Objects
import "js/historylib.js" as HistoryLib
import "js/math/latex.js" as LatexJS
import "js/historylib.mjs" as HistoryLib
/*!
@ -35,7 +33,6 @@ import "js/math/latex.js" as LatexJS
\sa LogarithmPlotter
*/
MenuBar {
property var settingsMenu: settingsSubMenu
Menu {
title: qsTr("&File")
@ -92,30 +89,36 @@ MenuBar {
icon.color: enabled ? sysPalette.windowText : sysPaletteIn.windowText
enabled: history.redoCount > 0
}
MenuSeparator { }
Action {
text: qsTr("&Copy plot")
shortcut: StandardKey.Copy
onTriggered: root.copyDiagramToClipboard()
icon.name: 'edit-copy'
}
MenuSeparator { }
Action {
text: qsTr("&Preferences")
shortcut: StandardKey.Copy
onTriggered: preferences.open()
icon.name: 'settings'
}
}
Menu {
title: qsTr("&Create")
// Services repeater
Repeater {
model: Object.keys(Objects.types)
model: Object.keys(Modules.Objects.types)
MenuItem {
text: Objects.types[modelData].displayType()
visible: Objects.types[modelData].createable()
text: Modules.Objects.types[modelData].displayType()
visible: Modules.Objects.types[modelData].createable()
height: visible ? implicitHeight : 0
icon.name: modelData
icon.source: './icons/objects/' + modelData + '.svg'
icon.color: sysPalette.buttonText
onTriggered: {
var newObj = Objects.createNewRegisteredObject(modelData)
var newObj = Modules.Objects.createNewRegisteredObject(modelData)
history.addToHistory(new HistoryLib.CreateNewObject(newObj.name, modelData, newObj.export()))
objectLists.update()
}
@ -123,105 +126,6 @@ MenuBar {
}
}
Menu {
id: settingsSubMenu
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'
icon.color: sysPalette.buttonText
}
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'
icon.color: sysPalette.buttonText
}
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'
icon.color: sysPalette.buttonText
}
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'
icon.color: sysPalette.buttonText
}
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'
icon.color: sysPalette.buttonText
}
Action {
id: autocompleteFormulaSetting
text: qsTr("Enable autocompletion")
checkable: true
checked: Helper.getSettingBool("autocompletion.enabled")
onTriggered: {
Helper.setSettingBool("autocompletion.enabled", checked)
}
icon.name: 'label'
icon.color: sysPalette.buttonText
}
Menu {
id: colorSchemeSetting
title: qsTr("Color Scheme")
property var schemes: ["Breeze Light", "Breeze Dark", "Solarized", "Github Light", "Github Dark", "Nord", "Monokai"]
Repeater {
model: colorSchemeSetting.schemes
MenuItem {
text: modelData
checkable: true
checked: Helper.getSettingInt("expression_editor.color_scheme") == index
onTriggered: {
parent.children[Helper.getSettingInt("expression_editor.color_scheme")].checked = false
checked = true
Helper.setSettingInt("expression_editor.color_scheme", index)
}
}
}
}
}
}
Menu {
title: qsTr("&Help")
Action {

View file

@ -1,6 +1,6 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -19,9 +19,7 @@
import QtQuick
import QtQml
import QtQuick.Window
import "../js/objects.js" as Objects
import "../js/historylib.js" as HistoryLib
import "../js/history/common.js" as HistoryCommon
import "../js/historylib.mjs" as HistoryLib
/*!
\qmltype History
@ -214,8 +212,8 @@ Item {
}
Component.onCompleted: {
HistoryLib.history = historyObj
HistoryCommon.themeTextColor = sysPalette.windowText
HistoryCommon.imageDepth = Screen.devicePixelRatio
Modules.History.history = historyObj
Modules.History.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.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -19,7 +19,7 @@
import QtQuick.Controls
import QtQuick
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import "../js/utils.js" as Utils
import "../js/utils.mjs" as Utils
/*!
@ -54,6 +54,7 @@ Item {
anchors.right: parent.right
anchors.top: parent.top
placeholderText: qsTr("Filter...")
category: "all"
}
ScrollView {

View file

@ -1,6 +1,6 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -19,7 +19,7 @@
import QtQuick.Controls
import QtQuick
import Qt5Compat.GraphicalEffects
import "../js/utils.js" as Utils
import "../js/utils.mjs" as Utils
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting

View file

@ -1,6 +1,6 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,9 +18,8 @@
import QtQuick
import Qt.labs.platform as Native
import "js/objects.js" as Objects
import "js/utils.js" as Utils
import "js/mathlib.js" as MathLib
import "js/utils.mjs" as Utils
import "js/mathlib.mjs" as MathLib
/*!
\qmltype LogGraphCanvas
@ -127,37 +126,6 @@ Canvas {
*/
property int maxgradx: 20
/*!
\qmlproperty var LogGraphCanvas::yaxisstepExpr
Expression for the y axis step (used to create labels).
*/
property var yaxisstepExpr: (new MathLib.Expression(`x*(${yaxisstep})`))
/*!
\qmlproperty double LogGraphCanvas::yaxisstep1
Value of the for the y axis step.
*/
property double yaxisstep1: yaxisstepExpr.execute(1)
/*!
\qmlproperty int LogGraphCanvas::drawMaxY
Minimum value of y that should be drawn onto the canvas.
*/
property int drawMaxY: Math.ceil(Math.max(Math.abs(ymax), Math.abs(px2y(canvasSize.height)))/yaxisstep1)
/*!
\qmlproperty var LogGraphCanvas::xaxisstepExpr
Expression for the x axis step (used to create labels).
*/
property var xaxisstepExpr: (new MathLib.Expression(`x*(${xaxisstep})`))
/*!
\qmlproperty double LogGraphCanvas::xaxisstep1
Value of the for the x axis step.
*/
property double xaxisstep1: xaxisstepExpr.execute(1)
/*!
\qmlproperty int LogGraphCanvas::drawMaxX
Maximum value of x that should be drawn onto the canvas.
*/
property int drawMaxX: Math.ceil(Math.max(Math.abs(xmin), Math.abs(px2x(canvasSize.width)))/xaxisstep1)
/*!
\qmlproperty var LogGraphCanvas::imageLoaders
Dictionary of format {image: [callback.image data]} containing data for defered image loading.
@ -169,7 +137,10 @@ Canvas {
*/
property var ctx
Component.onCompleted: imageLoaders = {}
Component.onCompleted: {
imageLoaders = {}
Modules.Canvas.initialize(canvas, drawingErrorDialog)
}
Native.MessageDialog {
id: drawingErrorDialog
@ -184,27 +155,7 @@ Canvas {
onPaint: function(rect) {
//console.log('Redrawing')
if(rect.width == canvas.width) { // Redraw full canvas
ctx = getContext("2d");
reset(ctx)
drawGrille(ctx)
drawAxises(ctx)
drawLabels(ctx)
ctx.lineWidth = linewidth
for(var objType in Objects.currentObjects) {
for(var obj of Objects.currentObjects[objType]){
ctx.strokeStyle = obj.color
ctx.fillStyle = obj.color
if(obj.visible)
try {
obj.draw(canvas, ctx)
} catch(e) {
// Drawing throws an error. Generally, it's due to a new modification (or the opening of a file)
drawingErrorDialog.showDialog(objType, obj.name, e.message)
history.undo()
}
}
}
ctx.lineWidth = 1
Modules.Canvas.redraw()
}
}
@ -218,172 +169,7 @@ 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*(xpow==1)))
}
} 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(isVisible(x, ymax)) {
drawLine(ctx, x2px(x), 0, x2px(x), canvasSize.height)
}
}
/*!
\qmlmethod void LogGraphCanvas::drawXLine(var ctx, double x)
Draws an vertical line at \c y plot coordinate using 2D \c ctx.
*/
function drawYLine(ctx, y) {
if(isVisible(xmin, y)) {
drawLine(ctx, 0, y2px(y), canvasSize.width, y2px(y))
}
}
/*!
\qmlmethod void LogGraphCanvas::drawVisibleText(var ctx, string text, double x, double y)
Writes multline \c text onto the canvas using 2D \c ctx.
\note The \c x and \c y properties here are relative to the canvas, not the plot.
*/
function drawVisibleText(ctx, text, x, y) {
if(x > 0 && x < canvasSize.width && y > 0 && y < canvasSize.height) {
text.toString().split("\n").forEach(function(txt, i){
ctx.fillText(txt, x, y+(canvas.textsize*i))
})
}
}
/*!
\qmlmethod void LogGraphCanvas::drawVisibleImage(var ctx, var image, double x, double y)
Draws an \c image onto the canvas using 2D \c ctx.
\note The \c x, \c y \c width and \c height properties here are relative to the canvas, not the plot.
*/
function drawVisibleImage(ctx, image, x, y, width, height) {
//console.log("Drawing image", isImageLoaded(image), isImageError(image))
markDirty(Qt.rect(x, y, width, height));
ctx.drawImage(image, x, y, width, height)
/*if(true || (x > 0 && x < canvasSize.width && y > 0 && y < canvasSize.height)) {
}*/
}
/*!
\qmlmethod var LogGraphCanvas::measureText(var ctx, string text)
Measures the wicth and height of a multiline \c text that would be drawn onto the canvas using 2D \c ctx.
Return format: dictionary {"width": width, "height": height}
*/
function measureText(ctx, text) {
let theight = 0
let twidth = 0
let defaultHeight = ctx.measureText("M").width // Approximate but good enough!
text.split("\n").forEach(function(txt, i){
theight += defaultHeight
if(ctx.measureText(txt).width > twidth) twidth = ctx.measureText(txt).width
})
return {'width': twidth, 'height': theight}
}
/*!
/*!
\qmlmethod double LogGraphCanvas::x2px(double x)
Converts an \c x coordinate to it's relative position on the canvas.
It supports both logarithmic and non logarithmic scale depending on the currently selected mode.
@ -423,71 +209,4 @@ Canvas {
function px2y(px) {
return -(px/yzoom-ymax)
}
/*!
\qmlmethod bool LogGraphCanvas::isVisible(double x, double y)
Checks whether a plot point (\c x, \c y) is visible or not on the canvas.
*/
function isVisible(x, y) {
return (x2px(x) >= 0 && x2px(x) <= canvasSize.width) && (y2px(y) >= 0 && y2px(y) <= canvasSize.height)
}
/*!
\qmlmethod bool LogGraphCanvas::drawLine(var ctx, double x1, double y1, double x2, double y2)
Draws a line from plot point (\c x1, \c y1) to plot point (\c x2, \¢ y2) using 2D \c ctx.
*/
function drawLine(ctx, x1, y1, x2, y2) {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
/*!
\qmlmethod bool LogGraphCanvas::drawDashedLine2(var ctx, double x1, double y1, double x2, double y2)
Draws a dashed line from plot point (\c x1, \c y1) to plot point (\c x2, \¢ y2) using 2D \c ctx.
*/
function drawDashedLine2(ctx, x1, y1, x2, y2, dashPxSize = 5) {
ctx.setLineDash([dashPxSize, dashPxSize]);
drawLine(ctx, x1, y1, x2, y2)
ctx.setLineDash([]);
}
/*!
\qmlmethod bool LogGraphCanvas::drawDashedLine(var ctx, double x1, double y1, double x2, double y2)
Draws a dashed line from plot point (\c x1, \c y1) to plot point (\c x2, \¢ y2) using 2D \c ctx.
(Legacy slower method)
*/
function drawDashedLine(ctx, x1, y1, x2, y2, dashPxSize = 10) {
var distance = Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2))
var progPerc = dashPxSize/distance
ctx.beginPath();
ctx.moveTo(x1, y1);
for(var i = 0; i < 1; i += progPerc) {
ctx.lineTo(x1-(x1-x2)*i, y1-(y1-y2)*i)
ctx.moveTo(x1-(x1-x2)*(i+progPerc/2), y1-(y1-y2)*(i+progPerc/2))
}
ctx.stroke();
}
/*!
\qmlmethod var LogGraphCanvas::renderLatexImage(string ltxText, color)
Renders latex markup \c ltxText to an image and loads it. Returns a dictionary with three values: source, width and height.
*/
function renderLatexImage(ltxText, color, callback) {
let [ltxSrc, ltxWidth, ltxHeight] = Latex.render(ltxText, textsize, color).split(",")
let imgData = {
"source": ltxSrc,
"width": parseFloat(ltxWidth),
"height": parseFloat(ltxHeight)
};
if(!isImageLoaded(ltxSrc) && !isImageLoading(ltxSrc)){
// Wait until the image is loaded to callback.
loadImage(ltxSrc)
imageLoaders[ltxSrc] = [callback, imgData]
} else {
// Callback directly
callback(canvas, ctx, imgData)
}
}
}

View file

@ -1,6 +1,6 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -21,11 +21,10 @@ import QtQuick.Controls
import eu.ad5001.MixedMenu 1.1
import QtQuick.Layouts 1.12
import QtQuick
// Auto loading all objects.
import "js/objs/autoload.js" as ALObjects
import "js/objects.js" as Objects
import "js/math/latex.js" as LatexJS
// Auto loading all modules.
import "js/autoload.js" as ModulesAutoload
import eu.ad5001.LogarithmPlotter.History 1.0
import eu.ad5001.LogarithmPlotter.ObjectLists 1.0
import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup
@ -45,16 +44,7 @@ ApplicationWindow {
color: sysPalette.window
title: "LogarithmPlotter " + (settings.saveFilename != "" ? " - " + settings.saveFilename.split('/').pop() : "") + (history.saved ? "" : "*")
SystemPalette {
id: sysPalette; colorGroup: SystemPalette.Active
Component.onCompleted: {
// LatexJS initialization.
LatexJS.enabled = Helper.getSettingBool("enable_latex")
LatexJS.Renderer = Latex
LatexJS.defaultColor = sysPalette.windowText
}
}
SystemPalette { id: sysPalette; colorGroup: SystemPalette.Active }
SystemPalette { id: sysPaletteIn; colorGroup: SystemPalette.Disabled }
menuBar: appMenu.trueItem
@ -65,6 +55,8 @@ ApplicationWindow {
Popup.GreetScreen {}
Popup.Preferences {id: preferences}
Popup.Changelog {id: changelog}
Popup.About {id: about}
@ -169,7 +161,7 @@ ApplicationWindow {
property bool firstDrawDone: false
onPainted: if(!firstDrawDone) {
onPainted: if(!firstDrawDone) {
firstDrawDone = true;
console.info("First paint done in " + (new Date().getTime()-(StartTime*1000)) + "ms")
if(TestBuild == true) {
@ -178,6 +170,13 @@ ApplicationWindow {
}
}
ViewPositionChangeOverlay {
id: viewPositionChanger
anchors.fill: parent
canvas: parent
settingsInstance: settings
}
PickLocationOverlay {
id: positionPicker
anchors.fill: parent
@ -185,130 +184,6 @@ 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 {
id: delayRefreshTimer
repeat: false
@ -330,6 +205,22 @@ ApplicationWindow {
}
}
/*!
\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()
}
}
/*!
\qmlmethod void LogarithmPlotter::copyDiagramToClipboard()
Copies the current diagram image to the clipboard.
@ -357,7 +248,7 @@ ApplicationWindow {
Action {
text: qsTr("&Update LogarithmPlotter")
icon.name: 'update'
onTriggered: Qt.openUrlExternally("https://dev.apps.ad5001.eu/logarithmplotter")
onTriggered: 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.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -20,10 +20,9 @@ import QtQuick
import QtQuick.Controls
import Qt.labs.platform as Native
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import "../../js/objects.js" as Objects
import "../../js/historylib.js" as HistoryLib
import "../../js/utils.js" as Utils
import "../../js/mathlib.js" as MathLib
import "../../js/historylib.mjs" as HistoryLib
import "../../js/utils.mjs" as Utils
import "../../js/mathlib.mjs" as MathLib
/*!
\qmltype CustomPropertyList
@ -91,7 +90,7 @@ Repeater {
root.changed()
}
}
}
}
}
@ -103,14 +102,21 @@ Repeater {
height: 30
label: propertyLabel
icon: `settings/custom/${propertyIcon}.svg`
isDouble: propertyType == 'number'
isDouble: propertyType == "number"
defValue: obj[propertyName] == null ? '' : obj[propertyName].toString()
category: {
return {
"Domain": "domain",
"string": "all",
"number": "all"
}[propertyType]
}
onChanged: function(newValue) {
try {
var newValueParsed = {
'Domain': () => MathLib.parseDomain(newValue),
'string': () => newValue,
'number': () => parseFloat(newValue)
"Domain": () => MathLib.parseDomain(newValue),
"string": () => newValue,
"number": () => parseFloat(newValue)
}[propertyType]()
// Ensuring old and new values are different to prevent useless adding to history.
@ -124,6 +130,7 @@ Repeater {
}
} catch(e) {
// Error in expression or domain
console.trace()
parsingErrorDialog.showDialog(propertyName, newValue, e.message)
}
}
@ -181,8 +188,8 @@ Repeater {
// Base, untranslated version of the model.
property var baseModel: selectObjMode ?
Objects.getObjectsName(propertyType.objType).concat(
isRealObject ? [qsTr("+ Create new %1").arg(Objects.types[propertyType.objType].displayType())] : [])
Modules.Objects.getObjectsName(propertyType.objType).concat(
isRealObject ? [qsTr("+ Create new %1").arg(Modules.Objects.types[propertyType.objType].displayType())] : [])
: propertyType.values
// Translated version of the model.
model: selectObjMode ? baseModel : propertyType.translatedValues
@ -192,20 +199,20 @@ Repeater {
if(selectObjMode) {
// This is only done when what we're selecting are Objects.
// Setting object property.
var selectedObj = Objects.currentObjectsByName[baseModel[newIndex]]
var selectedObj = Modules.Objects.currentObjectsByName[baseModel[newIndex]]
if(newIndex != 0) {
// Make sure we don't set the object to null.
if(selectedObj == null) {
// Creating new object.
selectedObj = Objects.createNewRegisteredObject(propertyType.objType)
selectedObj = Modules.Objects.createNewRegisteredObject(propertyType.objType)
history.addToHistory(new HistoryLib.CreateNewObject(selectedObj.name, propertyType.objType, selectedObj.export()))
baseModel = Objects.getObjectsName(propertyType.objType).concat(
isRealObject ? [qsTr("+ Create new %1").arg(Objects.types[propertyType.objType].displayType())] :
baseModel = Modules.Objects.getObjectsName(propertyType.objType).concat(
isRealObject ? [qsTr("+ Create new %1").arg(Modules.Objects.types[propertyType.objType].displayType())] :
[])
currentIndex = baseModel.indexOf(selectedObj.name)
}
selectedObj.requiredBy.push(Objects.currentObjects[objType][objIndex])
//Objects.currentObjects[objType][objIndex].requiredBy = obj[propertyName].filter((obj) => obj.name != obj.name)
selectedObj.requiredBy.push(Modules.Objects.currentObjects[objType][objIndex])
//Modules.Objects.currentObjects[objType][objIndex].requiredBy = obj[propertyName].filter((obj) => obj.name != obj.name)
}
obj.requiredBy = obj.requiredBy.filter((obj) => obj.name != obj.name)
history.addToHistory(new HistoryLib.EditedProperty(
@ -249,7 +256,7 @@ Repeater {
obj.name, objType, propertyName,
obj[propertyName], exported
))
//Objects.currentObjects[objType][objIndex][propertyName] = exported
//Modules.Objects.currentObjects[objType][objIndex][propertyName] = exported
obj[propertyName] = exported
root.changed()
}

View file

@ -1,6 +1,6 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -22,11 +22,9 @@ import QtQuick.Dialogs as D
import Qt.labs.platform as Native
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/objs/common.js" as ObjectsCommons
import "../../js/historylib.js" as HistoryLib
import "../../js/utils.js" as Utils
import "../../js/mathlib.js" as MathLib
import "../../js/historylib.mjs" as HistoryLib
import "../../js/utils.mjs" as Utils
import "../../js/mathlib.mjs" as MathLib
/*!
\qmltype Dialog
@ -54,7 +52,7 @@ Popup.BaseDialog {
\qmlproperty var EditorDialog::obj
Instance of the object being edited.
*/
property var obj: Objects.currentObjects[objType][objIndex]
property var obj: Modules.Objects.currentObjects[objType][objIndex]
/*!
\qmlproperty var EditorDialog::posPicker
Reference to the global PositionPicker QML object.
@ -87,7 +85,7 @@ Popup.BaseDialog {
Label {
id: dlgTitle
verticalAlignment: TextInput.AlignVCenter
text: qsTr("Edit properties of %1 %2").arg(Objects.types[objEditor.objType].displayType()).arg(objEditor.obj.name)
text: qsTr("Edit properties of %1 %2").arg(Modules.Objects.types[objEditor.objType].displayType()).arg(objEditor.obj.name)
font.pixelSize: 20
color: sysPalette.windowText
}
@ -107,19 +105,20 @@ Popup.BaseDialog {
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 Objects.currentObjectsByName) {
if(newName in Modules.Objects.currentObjectsByName) {
invalidNameDialog.showDialog(newName)
} else {
history.addToHistory(new HistoryLib.NameChanged(
objEditor.obj.name, objEditor.objType, newName
))
Objects.renameObject(obj.name, newName)
objEditor.obj = Objects.currentObjects[objEditor.objType][objEditor.objIndex]
Modules.Objects.renameObject(obj.name, newName)
objEditor.obj = Modules.Objects.currentObjects[objEditor.objType][objEditor.objIndex]
objectListList.update()
}
}
@ -164,7 +163,7 @@ Popup.BaseDialog {
*/
function open() {
dlgCustomProperties.model = [] // Reset
let objProps = Objects.types[objEditor.objType].properties()
let objProps = Modules.Objects.types[objEditor.objType].properties()
dlgCustomProperties.model = Object.keys(objProps).map(prop => [prop, objProps[prop]]) // Converted to 2-dimentional array.
objEditor.show()
}

View file

@ -1,6 +1,6 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,8 +18,7 @@
import QtQuick
import QtQuick.Controls
import "../js/objects.js" as Objects
import "../js/historylib.js" as HistoryLib
import "../js/historylib.mjs" as HistoryLib
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
@ -34,6 +33,21 @@ Column {
id: createRow
property var objectEditor
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 {
id: createTitle
@ -46,12 +60,12 @@ Column {
width: parent.width
columns: 3
Repeater {
model: Object.keys(Objects.types)
model: Object.keys(Modules.Objects.types)
Button {
id: createBtn
width: 96
visible: Objects.types[modelData].createable()
visible: Modules.Objects.types[modelData].createable()
height: visible ? width*0.8 : 0
// The KDE SDK is kinda buggy, so it respects neither specified color nor display propreties.
//display: AbstractButton.TextUnderIcon
@ -79,7 +93,7 @@ Column {
anchors.rightMargin: 4
horizontalAlignment: Text.AlignHCenter
font.pixelSize: 14
text: Objects.types[modelData].displayType()
text: Modules.Objects.types[modelData].displayType()
wrapMode: Text.WordWrap
clip: true
}
@ -89,13 +103,26 @@ Column {
ToolTip.text: label.text
onClicked: {
var newObj = Objects.createNewRegisteredObject(modelData)
let newObj = Modules.Objects.createNewRegisteredObject(modelData)
history.addToHistory(new HistoryLib.CreateNewObject(newObj.name, modelData, newObj.export()))
objectLists.update()
objectEditor.obj = Objects.currentObjects[modelData][Objects.currentObjects[modelData].length - 1]
objectEditor.objType = modelData
objectEditor.objIndex = Objects.currentObjects[modelData].length - 1
objectEditor.open()
let hasXProp = newObj.constructor.properties().hasOwnProperty('x')
let hasYProp = newObj.constructor.properties().hasOwnProperty('y')
if(hasXProp || hasYProp) {
// 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.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -21,7 +21,6 @@ import QtQuick
import QtQuick.Controls
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import eu.ad5001.LogarithmPlotter.ObjectLists.Editor 1.0 as Editor
import "../js/objects.js" as Objects
/*!
\qmltype ObjectLists
@ -47,7 +46,7 @@ ScrollView {
ListView {
id: objectsListView
model: Object.keys(Objects.types)
model: Object.keys(Modules.Objects.types)
//width: implicitWidth //objectListList.width - (implicitHeight > objectListList.parent.height ? 20 : 0)
implicitHeight: contentItem.childrenRect.height + footerItem.height + 10
@ -55,7 +54,7 @@ ScrollView {
id: objTypeList
property string objType: objectsListView.model[index]
property var editingRows: []
model: Objects.currentObjects[objType]
model: Modules.Objects.currentObjects[objType]
width: objectsListView.width
implicitHeight: contentItem.childrenRect.height
visible: model != undefined && model.length > 0
@ -70,21 +69,23 @@ ScrollView {
CheckBox {
id: typeVisibilityCheckBox
checked: Objects.currentObjects[objType] != undefined ? Objects.currentObjects[objType].every(obj => obj.visible) : true
checked: Modules.Objects.currentObjects[objType] != undefined ? Modules.Objects.currentObjects[objType].every(obj => obj.visible) : true
onClicked: {
for(var obj of Objects.currentObjects[objType]) obj.visible = this.checked
for(var obj of Modules.Objects.currentObjects[objType]) obj.visible = this.checked
for(var obj of objTypeList.editingRows) obj.objVisible = this.checked
objectListList.changed()
}
ToolTip.visible: hovered
ToolTip.text: checked ? qsTr("Hide all %1").arg(Objects.types[objType].displayTypeMultiple()) : qsTr("Show all %1").arg(Objects.types[objType].displayTypeMultiple())
ToolTip.text: checked ?
qsTr("Hide all %1").arg(Modules.Objects.types[objType].displayTypeMultiple()) :
qsTr("Show all %1").arg(Modules.Objects.types[objType].displayTypeMultiple())
}
Label {
id: typeHeaderText
verticalAlignment: TextInput.AlignVCenter
text: qsTranslate("control", "%1: ").arg(Objects.types[objType].displayTypeMultiple())
text: qsTranslate("control", "%1: ").arg(Modules.Objects.types[objType].displayTypeMultiple())
font.pixelSize: 20
}
}
@ -92,11 +93,11 @@ ScrollView {
delegate: ObjectRow {
id: controlRow
width: objTypeList.width
obj: Objects.currentObjects[objType][index]
obj: Modules.Objects.currentObjects[objType][index]
posPicker: positionPicker
onChanged: {
obj = Objects.currentObjects[objType][index]
obj = Modules.Objects.currentObjects[objType][index]
objectListList.update()
}
@ -110,6 +111,7 @@ ScrollView {
width: objectsListView.width
objectEditor: objEditor
objectLists: objectListList
posPicker: positionPicker
}
}
@ -127,7 +129,7 @@ ScrollView {
function update() {
objectListList.changed()
for(var objType in objectListList.listViews) {
objectListList.listViews[objType].model = Objects.currentObjects[objType]
objectListList.listViews[objType].model = Modules.Objects.currentObjects[objType]
}
}

View file

@ -1,6 +1,6 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -21,9 +21,7 @@ import QtQuick.Dialogs
import QtQuick.Controls
import QtQuick.Window
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import "../js/objects.js" as Objects
import "../js/historylib.js" as HistoryLib
import "../js/math/latex.js" as LatexJS
import "../js/historylib.mjs" as HistoryLib
/*!
@ -91,16 +89,16 @@ Item {
id: objDescription
anchors.left: objVisibilityCheckBox.right
anchors.right: deleteButton.left
height: LatexJS.enabled ? Math.max(parent.minHeight, latexDescription.height+4) : parent.minHeight
height: Modules.Latex.enabled ? Math.max(parent.minHeight, latexDescription.height+4) : parent.minHeight
verticalAlignment: TextInput.AlignVCenter
text: LatexJS.enabled ? "" : obj.getReadableString()
text: Modules.Latex.enabled ? "" : obj.getReadableString()
font.pixelSize: 14
Image {
id: latexDescription
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
visible: LatexJS.enabled
visible: Modules.Latex.enabled
property double depth: Screen.devicePixelRatio
property var ltxInfo: visible ? Latex.render(obj.getLatexString(), depth*(parent.font.pixelSize+2), parent.color).split(",") : ["","0","0"]
source: visible ? ltxInfo[0] : ""
@ -111,7 +109,7 @@ Item {
MouseArea {
anchors.fill: parent
onClicked: {
objEditor.obj = Objects.currentObjects[obj.type][index]
objEditor.obj = Modules.Objects.currentObjects[obj.type][index]
objEditor.objType = obj.type
objEditor.objIndex = index
//objEditor.editingRow = objectRow
@ -213,10 +211,14 @@ Item {
function deleteRecursively(object) {
for(let toRemove of object.requiredBy)
deleteRecursively(toRemove)
object.requiredBy = []
history.addToHistory(new HistoryLib.DeleteObject(
object.name, object.type, object.export()
))
Objects.deleteObject(object.name)
if(Modules.Objects.currentObjectsByName[object.name] != undefined) {
// Object still exists
// Temporary fix for objects require not being propertly updated.
object.requiredBy = []
history.addToHistory(new HistoryLib.DeleteObject(
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.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -19,9 +19,8 @@
import QtQuick
import QtQuick.Controls
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import "js/objects.js" as Objects
import "js/mathlib.js" as MathLib
import "js/historylib.js" as HistoryLib
import "js/mathlib.mjs" as MathLib
import "js/historylib.mjs" as HistoryLib
/*!
\qmltype PickLocationOverlay
@ -39,6 +38,14 @@ Item {
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
logGraphCanvas instance.
@ -106,7 +113,7 @@ Item {
if(mouse.button == Qt.LeftButton) { // Validate
let newValueX = !parent.userPickX ? null : parseValue(picked.mouseX.toString(), objType, propertyX)
let newValueY = !parent.userPickY ? null : parseValue(picked.mouseY.toString(), objType, propertyY)
let obj = Objects.currentObjectsByName[objName]
let obj = Modules.Objects.currentObjectsByName[objName]
// Set values
if(parent.userPickX && parent.userPickY) {
history.addToHistory(new HistoryLib.EditedPosition(
@ -116,6 +123,7 @@ Item {
obj[propertyY] = newValueY
obj.update()
objectLists.update()
pickerRoot.picked(obj)
} else if(parent.userPickX) {
history.addToHistory(new HistoryLib.EditedProperty(
objName, objType, propertyX, obj[propertyX], newValueX
@ -123,6 +131,7 @@ Item {
obj[propertyX] = newValueX
obj.update()
objectLists.update()
pickerRoot.picked(obj)
} else if(parent.userPickY) {
history.addToHistory(new HistoryLib.EditedProperty(
objName, objType, propertyY, obj[propertyY], newValueY
@ -130,6 +139,7 @@ Item {
obj[propertyY] = newValueY
obj.update()
objectLists.update()
pickerRoot.picked(obj)
}
}
pickerRoot.visible = false;
@ -252,7 +262,7 @@ Item {
color: 'black'
anchors.top: parent.top
anchors.left: parent.left
anchors.leftMargin: canvas.x2px(picked.mouseX)
anchors.leftMargin: Modules.Canvas.x2px(picked.mouseX)
visible: parent.userPickX
}
@ -263,7 +273,7 @@ Item {
color: 'black'
anchors.top: parent.top
anchors.left: parent.left
anchors.topMargin: canvas.y2px(picked.mouseY)
anchors.topMargin: Modules.Canvas.y2px(picked.mouseY)
visible: parent.userPickY
}
@ -272,25 +282,26 @@ Item {
x: picker.mouseX - width - 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: {
let xpos = canvas.px2x(picker.mouseX)
let xpos = Modules.Canvas.px2x(picker.mouseX)
if(snapToGridCheckbox.checked) {
if(canvas.logscalex) {
// Calculate the logged power
let pow = Math.pow(10, Math.floor(Math.log10(xpos)))
return pow*Math.round(xpos/pow)
} else {
return canvas.xaxisstep1*Math.round(xpos/canvas.xaxisstep1)
return axisX*Math.round(xpos/axisX)
}
} else {
return xpos.toFixed(parent.precision)
}
}
property double mouseY: {
let ypos = canvas.px2y(picker.mouseY)
let ypos = Modules.Canvas.px2y(picker.mouseY)
if(snapToGridCheckbox.checked) {
return canvas.yaxisstep1*Math.round(ypos/canvas.yaxisstep1)
return axisY*Math.round(ypos/axisY)
} else {
return ypos.toFixed(parent.precision)
}
@ -313,7 +324,7 @@ Item {
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) {
if(Objects.types[objType].properties()[propertyName] == 'number')
if(Modules.Objects.types[objType].properties()[propertyName] == 'number')
return parseFloat(value)
else
return new MathLib.Expression(value)

View file

@ -1,6 +1,6 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -102,7 +102,7 @@ BaseDialog {
wrapMode: Text.WordWrap
textFormat: Text.RichText
font.pixelSize: 13
text: "Copyright © 2023 Ad5001 &lt;mail@ad5001.eu&gt;<br>
text: "Copyright © 2021-2024 Ad5001 &lt;mail@ad5001.eu&gt;<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>
<br>

View file

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

View file

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

View file

@ -1,6 +1,6 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -32,7 +32,7 @@ Popup {
id: changelogPopup
x: (parent.width-width)/2
y: Math.max(20, (parent.height-height)/2)
width: changelog.width+40
width: 800
height: Math.min(parent.height-40, 500)
modal: true
focus: true
@ -44,33 +44,42 @@ Popup {
*/
property bool changelogNeedsFetching: true
onAboutToShow: if(changelogNeedsFetching) Helper.fetchChangelog()
onAboutToShow: if(changelogNeedsFetching) {
Helper.fetchChangelog()
}
Connections {
target: Helper
function onChangelogFetched(chl) {
changelogNeedsFetching = false;
changelog.text = chl
changelogView.contentItem.implicitHeight = changelog.height
// console.log(changelog.height, changelogView.contentItem.implicitHeight)
}
}
ScrollView {
id: changelogView
anchors.top: parent.top
anchors.topMargin: 10
anchors.left: parent.left
anchors.leftMargin: 10
anchors.right: parent.right
anchors.rightMargin: 10
anchors.bottom: doneBtn.top
anchors.bottomMargin: 10
clip: true
Label {
id: changelog
color: sysPalette.windowText
width: 760
wrapMode: Text.WordWrap
textFormat: TextEdit.MarkdownText
text: qsTr("Fetching changelog...")
onLinkActivated: Qt.openUrlExternally(link)
}
}
@ -87,7 +96,7 @@ Popup {
Button {
id: doneBtn
text: qsTr("Done")
text: qsTr("Close")
font.pixelSize: 18
anchors.bottom: parent.bottom
anchors.bottomMargin: 7

View file

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

View file

@ -1,6 +1,6 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,7 +18,7 @@
import QtQuick
import QtQuick.Controls
import "../js/math/latex.js" as Latex
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
/*!
\qmltype GreetScreen
@ -33,202 +33,122 @@ Popup {
id: greetingPopup
x: (parent.width-width)/2
y: Math.max(20, (parent.height-height)/2)
width: Math.max(welcome.width+70, checkForUpdatesSetting.width, resetRedoStackSetting.width)+20
width: greetingLayout.width+20
height: Math.min(parent.height-40, 700)
modal: true
focus: true
clip: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
ScrollView {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.bottomMargin: bottomButtons.height + 20
clip: true
Column {
width: greetingPopup.width - 25
spacing: 10
clip: true
topPadding: 35
Row {
id: welcome
height: logo.height
spacing: 10
anchors.horizontalCenter: parent.horizontalCenter
Image {
id: logo
source: "../icons/logarithmplotter.svg"
sourceSize.width: 48
sourceSize.height: 48
width: 48
height: 48
}
Label {
id: welcomeText
anchors.verticalCenter: parent.verticalCenter
wrapMode: Text.WordWrap
font.pixelSize: 32
text: qsTr("Welcome to LogarithmPlotter")
}
}
Label {
id: versionText
anchors.horizontalCenter: parent.horizontalCenter
wrapMode: Text.WordWrap
width: implicitWidth
font.pixelSize: 18
font.italic: true
text: qsTr("Version %1").arg(Helper.getVersion())
}
Label {
id: helpText
anchors.horizontalCenter: parent.horizontalCenter
wrapMode: Text.WordWrap
font.pixelSize: 14
width: parent.width - 50
text: qsTr("Take a few seconds to configure LogarithmPlotter.\nThese settings can be changed at any time from the \"Settings\" menu.")
}
CheckBox {
id: checkForUpdatesSetting
anchors.left: parent.left
checked: Helper.getSettingBool("check_for_updates")
text: qsTr('Check for updates on startup (requires online connectivity)')
onClicked: {
Helper.setSettingBool("check_for_updates", checked)
// Set in the menu bar
appMenu.settingsMenu.children[0].checked = checked
}
}
CheckBox {
id: resetRedoStackSetting
anchors.left: parent.left
checked: Helper.getSettingBool("reset_redo_stack")
text: qsTr('Reset redo stack when a new action is added to history')
onClicked: {
Helper.setSettingBool("reset_redo_stack", checked)
appMenu.settingsMenu.children[1].checked = checked
}
}
CheckBox {
id: enableLatexSetting
anchors.left: parent.left
checked: Helper.getSettingBool("enable_latex")
text: qsTr('Enable LaTeX rendering')
onClicked: {
Helper.setSettingBool("enable_latex", checked)
appMenu.settingsMenu.children[2].checked = checked
}
}
CheckBox {
id: autocloseFormulaSetting
anchors.left: parent.left
checked: Helper.getSettingBool("expression_editor.autoclose")
text: qsTr('Automatically close parenthesises and brackets in expressions')
onClicked: {
Helper.setSettingBool("expression_editor.autoclose", checked)
appMenu.settingsMenu.children[3].children[0].checked = checked
}
}
CheckBox {
id: colorizeFormulaSetting
anchors.left: parent.left
checked: Helper.getSettingBool("expression_editor.colorize")
text: qsTr('Enable syntax highlighting for expressions')
onClicked: {
Helper.setSettingBool("expression_editor.colorize", checked)
appMenu.settingsMenu.children[3].children[1].checked = checked
}
}
CheckBox {
id: autocompleteFormulaSetting
anchors.left: parent.left
checked: Helper.getSettingBool("autocompletion.enabled")
text: qsTr('Enable autocompletion interface in expression editor')
onClicked: {
Helper.setSettingBool("autocompletion.enabled", checked)
appMenu.settingsMenu.children[3].children[2].checked = checked
}
}
Row {
anchors.left: parent.left
anchors.leftMargin: 10
spacing: 10
Label {
id: colorSchemeLabel
anchors.verticalCenter: parent.verticalCenter
wrapMode: Text.WordWrap
text: qsTr("Color scheme:")
}
ComboBox {
model: ["Breeze Light", "Breeze Dark", "Solarized", "Github Light", "Github Dark", "Nord", "Monokai"]
currentIndex: Helper.getSettingInt("expression_editor.color_scheme")
onActivated: function(index) {
Helper.setSettingInt("expression_editor.color_scheme", index)
}
}
}
}
}
Rectangle {
id: bottomSeparator
opacity: 0.3
color: sysPalette.windowText
width: parent.width * 2 / 3
height: 1
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: bottomButtons.top
anchors.bottomMargin: 9
}
Row {
id: bottomButtons
anchors.bottom: parent.bottom
anchors.bottomMargin: 7
Column {
id: greetingLayout
width: 600
spacing: 10
anchors.horizontalCenter: parent.horizontalCenter
clip: true
topPadding: 35
Button {
id: userManualBtn
text: qsTr("User manual")
font.pixelSize: 18
onClicked: Qt.openUrlExternally("https://git.ad5001.eu/Ad5001/LogarithmPlotter/wiki/_Sidebar")
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")
}
}
Button {
id: changelogBtn
text: qsTr("Changelog")
Label {
id: versionText
anchors.horizontalCenter: parent.horizontalCenter
wrapMode: Text.WordWrap
width: implicitWidth
font.pixelSize: 18
onClicked: changelog.open()
}
Button {
id: doneBtn
text: qsTr("Done")
font.pixelSize: 18
onClicked: greetingPopup.close()
font.italic: true
text: qsTr("Version %1").arg(Helper.getVersion())
}
}
Grid {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: greetingLayout.bottom
anchors.topMargin: 50
columns: 2
spacing: 10
Repeater {
model: [{
name: qsTr("Changelog"),
icon: 'common/new.svg',
onClicked: () => changelog.open()
},{
name: qsTr("Preferences"),
icon: 'common/settings.svg',
onClicked: () => preferences.open()
},{
name: qsTr("User manual"),
icon: 'common/manual.svg',
onClicked: () => Qt.openUrlExternally("https://git.ad5001.eu/Ad5001/LogarithmPlotter/wiki/_Sidebar")
},{
name: qsTr("Close"),
icon: 'common/close.svg',
onClicked: () => greetingPopup.close()
}]
Button {
id: createBtn
width: 96
height: 96
onClicked: modelData.onClicked()
Component.onCompleted: if(Helper.getSetting("last_install_greet") != Helper.getVersion()) {
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) {
greetingPopup.open()
}

View file

@ -1,6 +1,6 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -31,8 +31,19 @@ Popup {
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
height: insertGrid.insertChars.length/insertGrid.columns*(width/insertGrid.columns)
height: Math.ceil(insertGrid.insertChars.length/insertGrid.columns)*(width/insertGrid.columns)+5
modal: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
@ -41,17 +52,40 @@ Popup {
width: parent.width
columns: 7
property var insertChars: [
property var insertCharsExpression: [
"∞","π","¹","²","³","⁴","⁵",
"⁶","⁷","⁸","⁹","⁰"
]
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 {
model: parent.insertChars.length

View file

@ -0,0 +1,257 @@
/**
* 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.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View file

@ -8,3 +8,4 @@ GreetScreen 1.0 GreetScreen.qml
Changelog 1.0 Changelog.qml
ThanksTo 1.0 ThanksTo.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.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -113,9 +113,11 @@ ListView {
Text {
id: annotationText
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
topPadding: 2
bottomPadding: 2
rightPadding: 15
font.pixelSize: autocompleteText.font.pixelSize - 2
text: listFiltered.model[index].annotation
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.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -114,6 +114,7 @@ Item {
anchors.left: iconLabel.right
anchors.leftMargin: icon == "" ? 0 : 5
height: 30
width: Math.max(85, implicitWidth)
anchors.top: parent.top
verticalAlignment: TextInput.AlignVCenter
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.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -20,10 +20,9 @@ import QtQuick.Controls
import QtQuick
import Qt.labs.platform as Native
import eu.ad5001.LogarithmPlotter.Popup 1.0 as P
import "../js/mathlib.js" as MathLib
import "../js/utils.js" as Utils
import "../js/objects.js" as Objects
import "../js/parsing/parsing.js" as Parsing
import "../js/mathlib.mjs" as MathLib
import "../js/utils.mjs" as Utils
import "../js/parsing/parsing.mjs" as Parsing
/*!
@ -81,6 +80,17 @@ Item {
Icon path of the editor.
*/
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
@ -178,8 +188,9 @@ Item {
id: labelItem
anchors.left: iconLabel.right
anchors.leftMargin: icon == "" ? 0 : 5
height: parent.height
anchors.top: parent.top
height: parent.height
width: Math.max(85, implicitWidth)
verticalAlignment: TextInput.AlignVCenter
//color: sysPalette.windowText
text: visible ? qsTranslate("control", "%1: ").arg(control.label) : ""
@ -381,7 +392,7 @@ Item {
id: objectPropertiesList
category: qsTr("Object Properties")
visbilityCondition: doesObjectExist
visbilityCondition: control.allowGraphObjects && doesObjectExist
itemStartIndex: 0
itemSelected: parent.itemSelected
property bool isEnteringProperty: (
@ -392,9 +403,9 @@ Item {
property string objectName: isEnteringProperty ?
(parent.currentToken.dot ? parent.previousToken.value : parent.previousToken2.value)
: ""
property bool doesObjectExist: isEnteringProperty && (objectName in Objects.currentObjectsByName)
property bool doesObjectExist: isEnteringProperty && (objectName in Modules.Objects.currentObjectsByName)
property var objectProperties: doesObjectExist ?
Objects.currentObjectsByName[objectName].constructor.properties() :
Modules.Objects.currentObjectsByName[objectName].constructor.properties() :
{}
categoryItems: Object.keys(objectProperties)
autocompleteGenerator: (item) => {
@ -433,7 +444,7 @@ Item {
itemSelected: parent.itemSelected
categoryItems: Parsing.CONSTANTS_LIST
autocompleteGenerator: (item) => {return {
'text': item, 'annotation': '',
'text': item, 'annotation': Parsing.CONSTANTS[item],
'autocomplete': item + " ", 'cursorFinalOffset': 0
}}
baseText: parent.visible ? parent.currentToken.value : ""
@ -448,7 +459,7 @@ Item {
itemSelected: parent.itemSelected
categoryItems: Parsing.FUNCTIONS_LIST
autocompleteGenerator: (item) => {return {
'text': item, 'annotation': '',
'text': item, 'annotation': Parsing.FUNCTIONS_USAGE[item].join(', '),
'autocomplete': item+'()', 'cursorFinalOffset': -1
}}
baseText: parent.visible ? parent.currentToken.value : ""
@ -458,12 +469,12 @@ Item {
id: executableObjectsList
category: qsTr("Executable Objects")
visbilityCondition: parent.currentToken.identifier && !parent.previousToken.dot
visbilityCondition: control.allowGraphObjects && parent.currentToken.identifier && !parent.previousToken.dot
itemStartIndex: functionsList.itemStartIndex + functionsList.model.length
itemSelected: parent.itemSelected
categoryItems: Objects.getObjectsName("ExecutableObject").filter(obj => obj != self)
categoryItems: Modules.Objects.getObjectsName("ExecutableObject").filter(obj => obj != self)
autocompleteGenerator: (item) => {return {
'text': item, 'annotation': Objects.currentObjectsByName[item] == null ? '' : Objects.currentObjectsByName[item].constructor.displayType(),
'text': item, 'annotation': Modules.Objects.currentObjectsByName[item] == null ? '' : Objects.currentObjectsByName[item].constructor.displayType(),
'autocomplete': item+'()', 'cursorFinalOffset': -1
}}
baseText: parent.visible ? parent.currentToken.value : ""
@ -473,12 +484,12 @@ Item {
id: objectsList
category: qsTr("Objects")
visbilityCondition: parent.currentToken.identifier && !parent.previousToken.dot
visbilityCondition: control.allowGraphObjects && parent.currentToken.identifier && !parent.previousToken.dot
itemStartIndex: executableObjectsList.itemStartIndex + executableObjectsList.model.length
itemSelected: parent.itemSelected
categoryItems: Object.keys(Objects.currentObjectsByName).filter(obj => obj != self)
categoryItems: Object.keys(Modules.Objects.currentObjectsByName).filter(obj => obj != self)
autocompleteGenerator: (item) => {return {
'text': item, 'annotation': `${Objects.currentObjectsByName[item].constructor.displayType()}`,
'text': item, 'annotation': `${Modules.Objects.currentObjectsByName[item].constructor.displayType()}`,
'autocomplete': item+'.', 'cursorFinalOffset': 0
}}
baseText: parent.visible ? parent.currentToken.value : ""
@ -507,6 +518,8 @@ Item {
x: Math.round((parent.width - width) / 2)
y: Math.round((parent.height - height) / 2)
category: "expression"
onSelected: function(c) {
editor.insert(editor.cursorPosition, c)
insertPopup.close()
@ -536,8 +549,8 @@ Item {
throw new Error(qsTranslate('error', 'Object cannot be dependent on itself.'))
// Recursive dependencies
let dependentOnSelfObjects = expr.requiredObjects().filter(
(obj) => Objects.currentObjectsByName[obj].getDependenciesList()
.includes(Objects.currentObjectsByName[control.self])
(obj) => Modules.Objects.currentObjectsByName[obj].getDependenciesList()
.includes(Modules.Objects.currentObjectsByName[control.self])
)
if(dependentOnSelfObjects.length == 1)
throw new Error(qsTranslate('error', 'Circular dependency detected. Object %1 depends on %2.').arg(dependentOnSelfObjects[0].toString()).arg(control.self))

View file

@ -1,6 +1,6 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -17,7 +17,7 @@
*/
import QtQuick
import QtQuick.Window
import Qt5Compat.GraphicalEffects
import QtQuick.Controls.impl
/*!
\qmltype Icon
@ -41,20 +41,16 @@ Item {
\qmlproperty string Icon::source
Path of the icon image source.
*/
property alias sourceSize: img.sourceSize.width
property alias sourceSize: img.sourceS
Image {
ColorImage {
id: img
height: parent.height
width: parent.width
visible: false
sourceSize.width: width*Screen.devicePixelRatio
sourceSize.height: width*Screen.devicePixelRatio
}
ColorOverlay {
anchors.fill: img
source: img
// visible: false
property int sourceS: width*Screen.devicePixelRatio
sourceSize.width: sourceS
sourceSize.height: sourceS
color: parent.color
}
}

View file

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

View file

@ -1,6 +1,6 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -49,6 +49,12 @@ Item {
If true, the input is being parsed an double before being emitting the \a changed signal.
*/
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
Minimum value for numbers that can be entered into the input.
@ -94,14 +100,14 @@ Item {
id: labelItem
anchors.left: iconLabel.right
anchors.leftMargin: icon == "" ? 0 : 5
height: parent.height
anchors.top: parent.top
height: parent.height
width: Math.max(85, implicitWidth)
verticalAlignment: TextInput.AlignVCenter
//color: sysPalette.windowText
text: visible ? qsTranslate("control", "%1: ").arg(control.label) : ""
visible: control.label != ""
}
TextField {
id: input
@ -122,8 +128,10 @@ Item {
onEditingFinished: function() {
if(insertButton.focus || insertPopup.focus) return
var value = text
if(control.isInt) value = Math.max(control.min,parseInt(value).toString()=="NaN"?control.min:parseInt(value))
if(control.isDouble) value = Math.max(control.min,parseFloat(value).toString()=="NaN"?control.min:parseFloat(value))
if(control.isInt)
value = isNaN(parseInt(value)) ? control.min : Math.max(control.min,parseInt(value))
if(control.isDouble)
value = isNaN(parseFloat(value)) ? control.min : Math.max(control.min,parseFloat(value))
if(value != "" && value.toString() != defValue) {
control.changed(value)
defValue = value.toString()

View file

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

View file

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

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

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1 @@
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd"><path d="M14 0v10l2-1.518 2 1.518v-10h4v24h-17c-1.657 0-3-1.343-3-3v-18c0-1.657 1.343-3 3-3h9zm6 20h-14.505c-1.375 0-1.375 2 0 2h14.505v-2z"/></svg>

After

Width:  |  Height:  |  Size: 251 B

View file

@ -0,0 +1 @@
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd"><path d="M12 0l-2.138 2.63-3.068-1.441-.787 3.297-3.389.032.722 3.312-3.039 1.5 2.088 2.671-2.088 2.67 3.039 1.499-.722 3.312 3.389.033.787 3.296 3.068-1.441 2.138 2.63 2.139-2.63 3.068 1.441.786-3.296 3.39-.033-.722-3.312 3.038-1.499-2.087-2.67 2.087-2.671-3.038-1.5.722-3.312-3.39-.032-.786-3.297-3.068 1.441-2.139-2.63zm0 15.5c.69 0 1.25.56 1.25 1.25s-.56 1.25-1.25 1.25-1.25-.56-1.25-1.25.56-1.25 1.25-1.25zm1-1.038v-7.462h-2v7.462h2z"/></svg>

After

Width:  |  Height:  |  Size: 550 B

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

After

Width:  |  Height:  |  Size: 19 B

View file

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

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 19 B

View file

@ -1,44 +1,17 @@
<?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="Function.svg"
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
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="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
id="metadata10">
<rdf:RDF>
@ -47,8 +20,7 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
<dc:date>2021</dc:date>
<dc:date>2021-2023</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>Ad5001</dc:title>
@ -56,7 +28,7 @@
</dc:creator>
<dc:rights>
<cc:Agent>
<dc:title>(C) Ad5001 2021 - Licensed under CC4.0-BY-NC-SA</dc:title>
<dc:title>(C) Ad5001 2021-2023 - Licensed under CC4.0-BY-NC-SA</dc:title>
</cc:Agent>
</dc:rights>
<cc:license
@ -78,33 +50,46 @@
</rdf:RDF>
</metadata>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1">
<path
id="rect1415"
style="fill:#000000;fill-rule:evenodd"
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"
sodipodi:nodetypes="ccccccccccccccccc" />
<path
id="rect839"
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
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"
sodipodi:nodetypes="ccccccc" />
<path
id="rect857"
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
d="m 12,10 h 2 l 6,8 h -2 z"
sodipodi:nodetypes="ccccc" />
<path
id="rect857-7"
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
d="m 12,18 h 2 l 6,-8 h -2 z"
sodipodi:nodetypes="ccccc" />
<path
id="rect839-3"
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
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"
sodipodi:nodetypes="ccccccc" />
<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="0.012050295"
y="17.985596"
id="text1"><tspan
id="tspan1"
x="0.012050295"
y="17.985596"
style="font-size:17.3333px">f</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="10.913334"
y="18.134649"
id="text1-3"><tspan
id="tspan1-6"
x="10.913334"
y="18.134649"
style="font-size:17.3333px">x</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="6.3066678"
y="17.646639"
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>
</svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -1,47 +1,17 @@
<?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="Gain Bode.svg"
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
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="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
id="metadata1472">
<rdf:RDF>
@ -50,13 +20,40 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
<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>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1">
<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"
@ -67,15 +64,14 @@
y="17" />
<text
xml:space="preserve"
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.12666447"
y="12.134649"
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"
x="-0.105"
y="11.959"
id="text839"><tspan
sodipodi:role="line"
id="tspan837"
x="-0.12666447"
y="12.134649"
style="font-size:17.3333px">ω</tspan></text>
x="-0.105"
y="11.959"
style="font-size:17px">ω</tspan></text>
<circle
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
id="path837"
@ -86,7 +82,6 @@
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" />
d="m 20.686533,-6.169873 2.232051,-0.1339746 -0.06218,13.8923049 -2.23205,0.1339746 z" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -1,44 +1,17 @@
<?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="Phase Bode.svg"
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
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="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
id="metadata13">
<rdf:RDF>
@ -47,13 +20,40 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
<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>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:#000000;stroke-width:1.1547"
@ -65,8 +65,7 @@
<path
id="rect26-3"
style="fill:#000000;stroke-width:1.22474"
d="m 15,2 v 14 h 2 V 4 h 7 V 2 Z"
sodipodi:nodetypes="ccccccc" />
d="m 15,2 v 14 h 2 V 4 h 7 V 2 Z" />
<circle
style="fill:#000000;stroke-width:2.09999"
id="path45"
@ -75,14 +74,13 @@
r="4" />
<text
xml:space="preserve"
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.4359932"
y="11.702"
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"
x="6.1339936"
y="11.163"
id="text49"><tspan
sodipodi:role="line"
id="tspan47"
x="6.4359932"
y="11.702"
style="font-size:18px;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none">φ</tspan></text>
x="6.1339936"
y="11.163"
style="font-size:17px;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none">φ</tspan></text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -1 +1,67 @@
<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>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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: 772 B

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -1,45 +1,17 @@
<?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="Sequence.svg"
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
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="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
id="metadata13">
<rdf:RDF>
@ -48,56 +20,62 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
<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:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1">
<text
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"
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"
x="7.483983"
y="16.134649"
id="text908"><tspan
sodipodi:role="line"
id="tspan906"
x="7.483983"
y="16.134649"
style="font-size:17.3333px">u</tspan></text>
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>
<text
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"
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"
x="16.365566"
y="18.663307"
id="text912"
transform="scale(1.0000324,0.9999676)"><tspan
sodipodi:role="line"
id="tspan910"
x="16.365566"
y="18.663307"
style="font-size:17px;stroke-width:1.00003">n</tspan></text>
<g
aria-label="("
id="text852"
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"
transform="matrix(1.0022756,0,0,1.2616817,-0.26079098,-9.0560687)">
<path
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"
style="font-size:12px;stroke:#000000;stroke-width:0.4396;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path854" />
</g>
<g
aria-label="("
id="text852-3"
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"
transform="matrix(-1.0030304,0,0,1.2658306,24.414952,-9.1000412)">
<path
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"
style="font-size:12px;stroke:#000000;stroke-width:0.4396;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path854-6" />
</g>
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>
<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;-inkscape-font-specification:sans-serif;font-stretch:normal;font-variant:normal"
x="-0.69333196"
y="17.646639"
id="text2"><tspan
id="tspan2"
x="-0.69333196"
y="17.646639"
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>
<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;-inkscape-font-specification:sans-serif;font-stretch:normal;font-variant:normal"
x="18.806667"
y="17.646639"
id="text2-7"><tspan
id="tspan2-5"
x="18.806667"
y="17.646639"
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>
</svg>

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

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

Before

Width:  |  Height:  |  Size: 3.1 KiB

View file

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

Before

Width:  |  Height:  |  Size: 3.3 KiB

View file

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

Before

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -1,19 +1,19 @@
<?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="Text.svg"
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
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
id="defs1469" />
<sodipodi:namedview
@ -24,23 +24,38 @@
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.4"
inkscape:cx="13.763421"
inkscape:cy="16.975675"
inkscape:cx="13.772321"
inkscape:cy="8.4598214"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="true"
inkscape:window-width="1920"
inkscape:window-height="1011"
inkscape:window-height="1010"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1">
inkscape:window-maximized="1"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1">
<inkscape:grid
type="xygrid"
id="grid2039" />
id="grid2039"
originx="0"
originy="0"
spacingy="1"
spacingx="1"
units="px"
visible="true" />
<inkscape:grid
type="xygrid"
id="grid2058" />
id="grid2058"
originx="0"
originy="0"
spacingy="1"
spacingx="1"
units="px"
visible="true" />
</sodipodi:namedview>
<metadata
id="metadata1472">
@ -50,8 +65,37 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
<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>
<g
@ -68,10 +112,16 @@
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"
sodipodi:nodetypes="ccccccccccccc" />
<path
id="rect837"
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
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"
sodipodi:nodetypes="ccccccccccccccc" />
<text
xml:space="preserve"
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"
x="13.844"
y="17.387978"
id="text1"><tspan
sodipodi:role="line"
id="tspan1"
x="13.844"
y="17.387978"
style="font-size:17px">t</tspan></text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View file

@ -1,47 +1,17 @@
<?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="X Cursor.svg"
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
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="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
id="metadata1472">
<rdf:RDF>
@ -50,13 +20,40 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
<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>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1">
<g
aria-label="X"
@ -68,18 +65,18 @@
id="rect12"
width="2"
height="24"
x="17"
x="5"
y="0"
ry="2.14841e-13" />
<path
id="rect835"
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
d="m 5,2 h 2 l 8,14 h -2 z"
sodipodi:nodetypes="ccccc" />
<path
id="rect835-6"
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
d="M 15,2 H 13 L 5,16 h 2 z"
sodipodi:nodetypes="ccccc" />
<text
xml:space="preserve"
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"
x="10.915"
y="13.713"
id="text1"><tspan
id="tspan1"
x="10.915"
y="13.713"
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>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

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

View file

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

View file

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

View file

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

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

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,14 +16,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.pragma library
.import "../objects.js" as Objects
.import "create.js" as Create
import Objects from "../objects.mjs"
import CreateNewObject from "create.mjs"
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.
export default class DeleteObject extends CreateNewObject {
/**
* 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'}
icon(){return 'delete'}

View file

@ -1,6 +1,6 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,15 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.pragma library
import Objects from "../objects.mjs"
import Latex from "../math/latex.mjs"
import * as MathLib from "../mathlib.mjs"
import { Action } from "common.mjs"
import { DrawableObject } from "../objs/common.mjs"
.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 {
export default class EditedProperty extends Action {
// Action used everytime an object's property has been changed
type(){return 'EditedProperty'}
@ -33,7 +31,16 @@ class EditedProperty extends C.Action {
color(darkVer=false){
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) {
super(targetName, targetType)
this.targetProperty = targetProperty
@ -42,10 +49,10 @@ class EditedProperty extends C.Action {
this.newValue = newValue
this.propertyType = Objects.types[targetType].properties()[targetProperty]
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.newValue = new MathLib.Expression(this.newValue);
} else if(this.propertyType == "Domain") {
} else if(this.propertyType === "Domain") {
this.previousValue = MathLib.parseDomain(this.previousValue);
this.newValue = MathLib.parseDomain(this.newValue);
} else {
@ -70,7 +77,7 @@ class EditedProperty extends C.Action {
export() {
if(this.previousValue instanceof MathLib.Expression) {
return [this.targetName, this.targetType, this.targetProperty, this.previousValue.toEditableString(), this.newValue.toEditableString(), true]
} else if(this.previousValue instanceof Common.DrawableObject) {
} else if(this.previousValue instanceof DrawableObject) {
return [this.targetName, this.targetType, this.targetProperty, this.previousValue.name, this.newValue.name, true]
} else {
return [this.targetName, this.targetType, this.targetProperty, this.previousValue, this.newValue, false]
@ -110,7 +117,7 @@ class EditedProperty extends C.Action {
// HTML
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>'
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.nextHTML= this.renderLatexAsHtml(this.newValue.latexMarkup)
}

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,30 +18,27 @@
// 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.
.pragma library
.import "history/common.js" as Common
.import "history/create.js" as Create
.import "history/delete.js" as Delete
.import "history/editproperty.js" as EP
.import "history/position.js" as Pos
.import "history/visibility.js" as V
.import "history/name.js" as Name
.import "history/color.js" as Color
var history = null;
import { Action as A } from "history/common.mjs"
import Create from "history/create.mjs"
import Delete from "history/delete.mjs"
import EP from "history/editproperty.mjs"
import Pos from "history/position.mjs"
import V from "history/visibility.mjs"
import Name from "history/name.mjs"
import Color from "history/color.mjs"
var Action = Common.Action
var CreateNewObject = Create.CreateNewObject
var DeleteObject = Delete.DeleteObject
var EditedProperty = EP.EditedProperty
var EditedPosition = Pos.EditedPosition
var EditedVisibility = V.EditedVisibility
var NameChanged = Name.NameChanged
var ColorChanged = Color.ColorChanged
export const Action = A
export const CreateNewObject = Create
export const DeleteObject = Delete
export const EditedProperty = EP
export const EditedPosition = Pos
export const EditedVisibility = V
export const NameChanged = Name
export const ColorChanged = Color
var Actions = {
export const Actions = {
"Action": Action,
"CreateNewObject": CreateNewObject,
"DeleteObject": DeleteObject,

View file

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

@ -26,7 +26,8 @@ var ADDITIONAL_VARCHARS = [
"ₕ","ₖ","ₗ","ₘ","ₙ","ₚ","ₛ",
"ₜ","¹","²","³","⁴","⁵","⁶",
"⁷","⁸","⁹","⁰","₁","₂","₃",
"₄","₅","₆","₇","₈","₉","₀"
"₄","₅","₆","₇","₈","₉","₀",
"∞","π"
]
function Instruction(type, value) {
@ -520,7 +521,7 @@ Expression.prototype.substitute = function (variable, expr) {
};
Expression.prototype.evaluate = function (values) {
values = values || {};
values = Object.assign({}, values, this.parser.consts)
return evaluate(this.tokens, this, values);
};
@ -540,8 +541,9 @@ Expression.prototype.variables = function (options) {
var vars = [];
getSymbols(this.tokens, vars, options);
var functions = this.functions;
var consts = this.parser.consts
return vars.filter(function (name) {
return !(name in functions);
return !(name in functions) && !(name in consts);
});
};
@ -579,7 +581,7 @@ function TokenStream(parser, expression) {
this.unaryOps = parser.unaryOps;
this.binaryOps = parser.binaryOps;
this.ternaryOps = parser.ternaryOps;
this.consts = parser.consts;
this.builtinConsts = parser.builtinConsts;
this.expression = expression;
this.savedPosition = 0;
this.savedCurrent = null;
@ -691,7 +693,7 @@ TokenStream.prototype.isConst = function () {
var i = startPos;
for (; i < this.expression.length; i++) {
var c = this.expression.charAt(i);
if (c.toUpperCase() === c.toLowerCase()) {
if (c.toUpperCase() === c.toLowerCase() && !ADDITIONAL_VARCHARS.includes(c)) {
if (i === this.pos || (c !== '_' && c !== '.' && (c < '0' || c > '9'))) {
break;
}
@ -699,8 +701,8 @@ TokenStream.prototype.isConst = function () {
}
if (i > startPos) {
var str = this.expression.substring(startPos, i);
if (str in this.consts) {
this.current = this.newToken(TNUMBER, this.consts[str]);
if (str in this.builtinConsts) {
this.current = this.newToken(TNUMBER, this.builtinConsts[str]);
this.pos += str.length;
return true;
}
@ -1115,7 +1117,7 @@ ParserState.prototype.parseExpression = function (instr) {
if (this.parseUntilEndStatement(instr, exprInstr)) {
return;
}
this.parseVariableAssignmentExpression(exprInstr);
this.parseConditionalExpression(exprInstr);
if (this.parseUntilEndStatement(instr, exprInstr)) {
return;
}
@ -1155,37 +1157,6 @@ ParserState.prototype.parseArrayList = function (instr) {
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) {
this.parseOrExpression(instr);
while (this.accept(TOP, '?')) {
@ -1791,12 +1762,12 @@ class Parser {
join: arrayJoin
};
this.consts = {
E: Math.E,
PI: Math.PI,
'true': true,
'false': false
};
// These constants will automatically be replaced the MOMENT they are parsed.
// (Original consts from the parser)
this.builtinConsts = {};
// These consts will only be replaced when the expression is evaluated.
this.consts = {}
}
parse(expr) {

View file

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

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

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

View file

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

View file

@ -1,276 +0,0 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2023 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 text to convert
* @param {bool} wrapIn$ - checks whether the escaped chars should be escaped
* @returns {string}
*/
function variable(vari, wrapIn$ = false) {
let unicodechars = ["α","β","γ","δ","ε","ζ","η",
"π","θ","κ","λ","μ","ξ","ρ",
"ς","σ","τ","φ","χ","ψ","ω",
"Γ","Δ","Θ","Λ","Ξ","Π","Σ",
"Φ","Ψ","Ω","ₐ","ₑ","ₒ","ₓ",
"ₕ","ₖ","ₗ","ₘ","ₙ","ₚ","ₛ",
"ₜ","¹","²","³","⁴","⁵","⁶",
"⁷","⁸","⁹","⁰","₁","₂","₃",
"₄","₅","₆","₇","₈","₉","₀",
"pi"]
let equivalchars = ["\\alpha","\\beta","\\gamma","\\delta","\\epsilon","\\zeta","\\eta",
"\\pi","\\theta","\\kappa","\\lambda","\\mu","\\xi","\\rho",
"\\sigma","\\sigma","\\tau","\\phi","\\chi","\\psi","\\omega",
"\\Gamma","\\Delta","\\Theta","\\Lambda","\\Xi","\\Pi","\\Sigma",
"\\Phy","\\Psi","\\Omega","{}_{a}","{}_{e}","{}_{o}","{}_{x}",
"{}_{h}","{}_{k}","{}_{l}","{}_{m}","{}_{n}","{}_{p}","{}_{s}",
"{}_{t}","{}^{1}","{}^{2}","{}^{3}","{}^{4}","{}^{5}","{}^{6}",
"{}^{7}","{}^{8}","{}^{9}","{}^{0}","{}_{1}","{}_{2}","{}_{3}",
"{}_{4}","{}_{5}","{}_{6}","{}_{7}","{}_{8}","{}_{9}","{}_{0}",
"\\pi"]
if(wrapIn$)
for(let i = 0; i < unicodechars.length; i++) {
if(vari.includes(unicodechars[i]))
vari = vari.replace(new RegExp(unicodechars[i], 'g'), '$'+equivalchars[i]+'$')
}
else
for(let i = 0; i < unicodechars.length; i++) {
if(vari.includes(unicodechars[i]))
vari = vari.replace(new RegExp(unicodechars[i], 'g'), equivalchars[i])
}
return vari;
}
/**
* Converts expr-eval tokens to a latex string.
*
* @param {Array} tokens - expr-eval tokens list
* @returns {string}
*/
function expression(tokens) {
var nstack = [];
var n1, n2, n3;
var f, args, argCount;
for (var i = 0; i < tokens.length; i++) {
var item = tokens[i];
var type = item.type;
switch(type) {
case ExprEval.INUMBER:
if(item.value == Infinity) {
nstack.push("\\infty")
} else if(typeof item.value === 'number' && item.value < 0) {
nstack.push(par(item.value));
} else if(Array.isArray(item.value)) {
nstack.push('[' + item.value.map(ExprEval.escapeValue).join(', ') + ']');
} else {
nstack.push(ExprEval.escapeValue(item.value));
}
break;
case ExprEval.IOP2:
n2 = nstack.pop();
n1 = nstack.pop();
f = item.value;
switch(f) {
case '-':
case '+':
nstack.push(n1 + f + n2);
break;
case '||':
case 'or':
case '&&':
case 'and':
case '==':
case '!=':
nstack.push(par(n1) + f + par(n2));
break;
case '*':
if(n2 == "\\pi" || n2 == "e" || n2 == "x" || n2 == "n")
nstack.push(parif(n1,['+','-']) + n2)
else
nstack.push(parif(n1,['+','-']) + " \\times " + parif(n2,['+','-']));
break;
case '/':
nstack.push("\\frac{" + n1 + "}{" + n2 + "}");
break;
case '^':
nstack.push(parif(n1,['+','-','*','/','!']) + "^{" + n2 + "}");
break;
case '%':
nstack.push(parif(n1,['+','-','*','/','!','^']) + " \\mathrm{mod} " + parif(n2,['+','-','*','/','!','^']));
break;
case '[':
nstack.push(n1 + '[' + n2 + ']');
break;
default:
throw new EvalError("Unknown operator " + ope + ".");
}
break;
case ExprEval.IOP3: // Thirdiary operator
n3 = nstack.pop();
n2 = nstack.pop();
n1 = nstack.pop();
f = item.value;
if (f === '?') {
nstack.push('(' + n1 + ' ? ' + n2 + ' : ' + n3 + ')');
} else {
throw new EvalError('Unknown operator ' + ope + '.');
}
break;
case ExprEval.IVAR:
case ExprEval.IVARNAME:
nstack.push(variable(item.value.toString()));
break;
case ExprEval.IOP1: // Unary operator
n1 = nstack.pop();
f = item.value;
switch(f) {
case '-':
case '+':
nstack.push(par(f + n1));
break;
case '!':
nstack.push(parif(n1,['+','-','*','/','^']) + '!');
break;
default:
nstack.push(f + parif(n1,['+','-','*','/','^']));
break;
}
break;
case ExprEval.IFUNCALL:
argCount = item.value;
args = [];
while (argCount-- > 0) {
args.unshift(nstack.pop());
}
f = nstack.pop();
// Handling various functions
nstack.push(functionToLatex(f, args))
break;
case ExprEval.IFUNDEF:
nstack.push(par(n1 + '(' + args.join(', ') + ') = ' + n2));
break;
case ExprEval.IMEMBER:
n1 = nstack.pop();
nstack.push(n1 + '.' + item.value);
break;
case ExprEval.IARRAY:
argCount = item.value;
args = [];
while (argCount-- > 0) {
args.unshift(nstack.pop());
}
nstack.push('[' + args.join(', ') + ']');
break;
case ExprEval.IEXPR:
nstack.push('(' + expression(item.value) + ')');
break;
case ExprEval.IENDSTATEMENT:
break;
default:
throw new EvalError('invalid Expression');
break;
}
}
if (nstack.length > 1) {
nstack = [ nstack.join(';') ]
}
return String(nstack[0]);
}

View file

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

View file

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

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

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

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

@ -0,0 +1,42 @@
/**
* 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.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,42 +16,72 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.pragma library
.import "../utils.js" as Utils
.import "../objects.js" as Objects
.import "../math/latex.js" as Latex
.import "../parameters.js" as P
.import "../math/common.js" as C
import { getRandomColor, textsub } from "../utils.mjs"
import Objects from "../objects.mjs"
import Latex from "../math/latex.mjs"
import {Module} from "../modules.mjs"
import {ensureTypeSafety, serializesByPropertyType} from "../parameters.mjs"
// This file contains the default data to be imported from all other objects
/**
* Creates a new name for an object, based on the \c allowedLetters.
* If variables with each of the allowedLetters is created, a subscript
* number is added to the name.
* @param {string} prefix - Prefix to the name.
* @return {string} New unused name for a new object.
*/
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
var ret
do {
var letter = allowedLetters[newid % allowedLetters.length]
var num = Math.floor((newid - (newid % allowedLetters.length)) / allowedLetters.length)
ret = prefix + letter + (num > 0 ? Utils.textsub(num-1) : '')
newid += 1
} while(ret in Objects.currentObjectsByName)
return ret
class ObjectsCommonAPI extends Module {
constructor() {
super('ObjectsCommon', [
Modules.Objects,
Modules.ExprParser,
Modules.Latex
])
}
/**
* Creates a new name for an object, based on the allowedLetters.
* If variables with each of the allowedLetters is created, a subscript
* number is added to the name.
* @param {string} allowedLetters
* @param {string} prefix - Prefix to the name.
* @return {string} New unused name for a new object.
*/
getNewName(allowedLetters, prefix='') {
// 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
* can be drawn on the canvas.
*/
class DrawableObject {
export class DrawableObject {
/**
* Base name of the object. Needs to be constant over time.
* @return {string} Type of the object.
@ -73,7 +103,7 @@ class DrawableObject {
/**
* True if this object can be created by the user, false if it can only
* be instantiated by other objects
* @return {bool}
* @return {boolean}
*/
static createable() {return true}
@ -89,30 +119,44 @@ class DrawableObject {
* If the property is to be translated, the key should be passed
* through the QT_TRANSLATE_NOOP macro in that form:
* [QT_TRANSLATE_NOOP('prop','key')]
* Enums that are translated should be indexed in parameters.js and
* Enums that are translated should be indexed in parameters.mjs and
* then be linked directly here.
*
* @return {Dictionary}
* @return {Object.<string,string|Enum|List|ObjectType|Dictionary>}
*/
static properties() {return {}}
/**
* 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.
* @return {bool}
* @return {boolean}
*/
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.
* @param {string} name - Name of the object
* @param {bool} visible - true if the object is visible, false otherwise.
* @param {color} color - Color of the object (can be string or QColor)
* @param {boolean} visible - true if the object is visible, false otherwise.
* @param {color|string} 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.
* @constructor()
* @constructor
*/
constructor(name, visible = true, color = null, labelContent = 'name + value') {
if(color == null) color = Utils.getRandomColor()
if(color == null) color = getRandomColor()
this.type = this.constructor.type()
this.name = name
this.visible = visible
@ -121,15 +165,18 @@ class DrawableObject {
this.requiredBy = []
this.requires = []
}
/**
* Serilizes the object in an array that can be JSON serialized.
* Serializes the object in an array that can be JSON serialized.
* These parameters will be re-entered in the constructor when restored.
* @return {array}
*/
export() {
// Should return what will be inputed as arguments when a file is loaded (serializable form)
return [this.name, this.visible, this.color.toString(), this.labelContent]
let exportList = [this.name, this.visible, this.color.toString(), this.labelContent]
for(let [name, propType] of Object.entries(this.constructor.properties()))
if(!name.startsWith('comment'))
exportList.push(serializesByPropertyType(propType, this[name]))
return exportList
}
/**
@ -145,7 +192,7 @@ class DrawableObject {
* Latex markuped version of the readable string.
* Every non latin character should be passed as latex symbols and formulas
* should be in latex form.
* See ../latex.js for helper methods.
* See ../latex.mjs for helper methods.
* @return {string}
*/
getLatexString() {
@ -153,7 +200,7 @@ class DrawableObject {
}
/**
* Readable string content of the label depending on the value of the \c latexContent.
* Readable string content of the label depending on the value of the latexContent.
* @return {string}
*/
getLabel() {
@ -169,10 +216,10 @@ class DrawableObject {
}
/**
* Latex markup string content of the label depending on the value of the \c latexContent.
* Latex markup string content of the label depending on the value of the latexContent.
* Every non latin character should be passed as latex symbols and formulas
* should be in latex form.
* See ../latex.js for helper methods.
* See ../latex.mjs for helper methods.
* @return {string}
*/
getLatexLabel() {
@ -204,25 +251,26 @@ class DrawableObject {
update() {
// Refreshing dependencies.
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
this.requires = []
let currentObjectsByName = Objects.currentObjectsByName
let properties = this.constructor.properties()
for(let property in properties)
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
for(let objName of this[property].requiredObjects()) {
if(objName in C.currentObjectsByName && !this.requires.includes(C.currentObjectsByName[objName])) {
this.requires.push(C.currentObjectsByName[objName])
C.currentObjectsByName[objName].requiredBy.push(this)
if(objName in currentObjectsByName && !this.requires.includes(currentObjectsByName[objName])) {
this.requires.push(currentObjectsByName[objName])
currentObjectsByName[objName].requiredBy.push(this)
}
}
if(this[property].cached && this[property].requiredObjects().length > 0)
// Recalculate
this[property].recache()
} else if(properties[property].type == 'ObjectType' && this[property] != null) {
} else if(properties[property].type === 'ObjectType' && this[property] != null) {
// Object dependency
this.requires.push(this[property])
this[property].requiredBy.push(this)
@ -239,24 +287,26 @@ class DrawableObject {
for(let toRemove of this.requiredBy) { // Normally, there should be none here, but better leave nothing just in case.
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 \c canvas with the 2D context \c ctx.
* @param {Canvas} canvas
* @param {Context2D} ctx
* Abstract method. Draw the object onto the canvas with the.
* @param {CanvasAPI} canvas
*/
draw(canvas, ctx) {}
draw(canvas) {}
/**
* Applicates a \c drawFunction with two position arguments depending on
* both the \c posX and \c posY of where the label should be displayed,
* and the \c labelPosition which declares the label should be displayed
* Applicates a drawFunction with two position arguments depending on
* both the posX and posY of where the label should be displayed,
* and the labelPosition which declares the label should be displayed
* relatively to that 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 {Dictionary} size - Size of the label item, containing two properties, "width" and "height"
* @param {Object.<string, int>} 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} 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
@ -300,27 +350,26 @@ class DrawableObject {
}
/**
* Automatically draw text (by default the label of the object on the \c canvas with
* the 2D context \c ctx depending on user settings.
* This method takes into account both the \c posX and \c posY of where the label
* should be displayed, including the \c labelPosition relative to it.
* The text is get both through the \c getLatexFunction and \c getTextFunction
* Automatically draw text (by default the label of the object on the canvas
* depending on user settings.
* This method takes into account both the posX and posY of where the label
* should be displayed, including the labelPosition relative to it.
* The text is get both through the getLatexFunction and getTextFunction
* depending on whether to use latex.
* Then, it's displayed using the \c drawFunctionLatex (x,y,imageData) and
* \c drawFunctionText (x,y,text) depending on whether to use latex.
* Then, it's displayed using the drawFunctionLatex (x,y,imageData) and
* drawFunctionText (x,y,text) depending on whether to use latex.
*
* @param {Canvas} canvas
* @param {Context2D} ctx
* @param {CanvasAPI} canvas
* @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} posY - Component of the marked position on the y-axis
* @param {bool} forceText - Force the rendering of the label as text
* @param {boolean} 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} 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} drawFunctionText - Function (x,y,text,textSize) to display the text
*/
drawLabel(canvas, ctx, labelPosition, posX, posY, forceText = false,
drawLabel(canvas, labelPosition, posX, posY,forceText = false,
getLatexFunction = null, getTextFunction = null, drawFunctionLatex = null, drawFunctionText = null) {
// Default functions
if(getLatexFunction == null)
@ -328,25 +377,25 @@ class DrawableObject {
if(getTextFunction == null)
getTextFunction = this.getLabel.bind(this)
if(drawFunctionLatex == null)
drawFunctionLatex = (x,y,ltxImg) => canvas.drawVisibleImage(ctx, ltxImg.source, x, y, ltxImg.width, ltxImg.height)
drawFunctionLatex = (x,y,ltxImg) => canvas.drawVisibleImage(ltxImg.source, x, y, ltxImg.width, ltxImg.height)
if(drawFunctionText == null)
drawFunctionText = (x,y,text,textSize) => canvas.drawVisibleText(ctx, text, x, y+textSize.height) // Positioned from left bottom
drawFunctionText = (x,y,text,textSize) => canvas.drawVisibleText(text, x, y+textSize.height) // Positioned from left bottom
// Drawing the label
let offset
if(!forceText && Latex.enabled) { // TODO: Check for user setting with Latex.
if(!forceText && Latex.enabled) {
// With latex
let drawLblCb = function(canvas, ctx, ltxImg) {
this.drawPositionDivergence(labelPosition, 8+ctx.lineWidth/2, ltxImg, posX, posY, (x,y) => drawFunctionLatex(x,y,ltxImg))
}
let drawLblCb = ((ltxImg) => {
this.drawPositionDivergence(labelPosition, 8+canvas.linewidth/2, ltxImg, posX, posY, (x,y) => drawFunctionLatex(x,y,ltxImg))
}).bind(this)
let ltxLabel = getLatexFunction();
if(ltxLabel != "")
if(ltxLabel !== "")
canvas.renderLatexImage(ltxLabel, this.color, drawLblCb.bind(this))
} else {
// Without latex
let text = getTextFunction()
ctx.font = `${canvas.textsize}px sans-serif`
let textSize = canvas.measureText(ctx, text)
this.drawPositionDivergence(labelPosition, 8+ctx.lineWidth/2, textSize, posX, posY, (x,y) => drawFunctionText(x,y,text,textSize))
canvas.font = `${canvas.textsize}px sans-serif`
let textSize = canvas.measureText(text)
this.drawPositionDivergence(labelPosition, 8+canvas.linewidth/2, textSize, posX, posY, (x,y) => drawFunctionText(x,y,text,textSize))
}
}
@ -355,6 +404,7 @@ class DrawableObject {
}
}
/**
* Class to be extended for every object on which
* an y for a x can be computed with the execute function.
@ -362,7 +412,7 @@ class DrawableObject {
* return null. However, theses same x values will
* return false when passed to canExecute.
*/
class ExecutableObject extends DrawableObject {
export class ExecutableObject extends DrawableObject {
/**
* Returns the corresponding y value for an x value.
* If the object isn't defined on the given x, then
@ -376,38 +426,25 @@ class ExecutableObject extends DrawableObject {
* Returns false if the object isn't defined on the given x, true otherwise.
*
* @param {number} x
* @returns {bool}
* @returns {boolean}
*/
canExecute(x = 1) {return true}
/**
* Returns the simplified expression string for a given x.
*
* @param {number} x
* @returns {bool}
* @returns {string}
*/
simplify(x = 1) {return '0'}
/**
* 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.
* @return {bool}
* @return {boolean}
*/
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.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,16 +16,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.pragma library
.import "common.js" as Common
.import "../utils.js" as Utils
.import "../mathlib.js" as MathLib
.import "../parameters.js" as P
.import "../math/latex.js" as Latex
import { textsub } from "../utils.mjs"
import { API as Common, ExecutableObject } from "common.mjs"
import { parseDomain, Expression, SpecialDomain } from "../mathlib.mjs"
import * as P from "../parameters.mjs"
import Latex from "../math/latex.mjs"
class Function extends Common.ExecutableObject {
export default class Function extends ExecutableObject {
static type(){return 'Function'}
static displayType(){return qsTr('Function')}
static displayTypeMultiple(){return qsTr('Functions')}
@ -37,8 +35,8 @@ class Function extends Common.ExecutableObject {
'comment',
'Ex: R+* (ℝ⁺*), N (), Z-* (ℤ⁻*), ]0;1[, {3;4;5}'
),
[QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position,
[QT_TRANSLATE_NOOP('prop','displayMode')]: P.Enum.FunctionDisplayType,
[QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position,
[QT_TRANSLATE_NOOP('prop','labelX')]: 'number',
'comment2': QT_TRANSLATE_NOOP(
'comment',
@ -54,11 +52,11 @@ class Function extends Common.ExecutableObject {
drawPoints = true, drawDashedLines = true) {
if(name == null) name = Common.getNewName('fghjqlmnopqrstuvwabcde')
super(name, visible, color, labelContent)
if(typeof expression == 'number' || typeof expression == 'string') expression = new MathLib.Expression(expression.toString())
if(typeof expression == 'number' || typeof expression == 'string') expression = new Expression(expression.toString())
this.expression = expression
if(typeof definitionDomain == 'string') definitionDomain = MathLib.parseDomain(definitionDomain)
if(typeof definitionDomain == 'string') definitionDomain = parseDomain(definitionDomain)
this.definitionDomain = definitionDomain
if(typeof destinationDomain == 'string') destinationDomain = MathLib.parseDomain(destinationDomain)
if(typeof destinationDomain == 'string') destinationDomain = parseDomain(destinationDomain)
this.destinationDomain = destinationDomain
this.displayMode = displayMode
this.labelPosition = labelPosition
@ -68,28 +66,22 @@ class Function extends Common.ExecutableObject {
}
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()}`
} else {
return `${this.name}(x) = ${this.expression.toString()}\nD${Utils.textsub(this.name)} = ${this.definitionDomain}`
return `${this.name}(x) = ${this.expression.toString()}\nD${textsub(this.name)} = ${this.definitionDomain}`
}
}
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}\\\\
x\\textrm{ } & \\mapsto & \\textrm{ }${this.expression.latexMarkup}\\end{array}`
} else {
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) {
if(this.definitionDomain.includes(x))
return this.expression.execute(x)
@ -106,36 +98,38 @@ class Function extends Common.ExecutableObject {
return ''
}
draw(canvas, ctx) {
Function.drawFunction(canvas, ctx, this.expression, this.definitionDomain, this.destinationDomain, this.drawPoints, this.drawDashedLines)
draw(canvas) {
Function.drawFunction(canvas, this.expression, this.definitionDomain, this.destinationDomain, this.drawPoints, this.drawDashedLines)
// Label
this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))
this.drawLabel(canvas, 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.
// Drawing small traits every 0.2px
var pxprecision = 1
var previousX = canvas.px2x(0)
var previousY;
if(definitionDomain instanceof MathLib.SpecialDomain && definitionDomain.moveSupported) {
/**
* Reusable in other objects.
* Drawing small traits every few pixels
*/
static drawFunction(canvas, expr, definitionDomain, destinationDomain, drawPoints = true, drawDash = true) {
let pxprecision = 10
let previousX = canvas.px2x(0)
let previousY = null;
if(definitionDomain instanceof SpecialDomain && definitionDomain.moveSupported) {
// Point based functions.
previousX = definitionDomain.next(previousX)
if(previousX === null) previousX = definitionDomain.next(canvas.px2x(0))
previousY = expr.execute(previousX)
if(!drawPoints && !drawDash) return
while(previousX !== null && canvas.x2px(previousX) < canvas.canvasSize.width) {
while(previousX !== null && canvas.x2px(previousX) < canvas.width) {
// Reconverted for canvas to fix for logarithmic scales.
var currentX = definitionDomain.next(canvas.px2x(canvas.x2px(previousX)+pxprecision));
var currentY = expr.execute(currentX)
let currentX = definitionDomain.next(canvas.px2x(canvas.x2px(previousX)+pxprecision));
let currentY = expr.execute(currentX)
if(currentX === null) break;
if((definitionDomain.includes(currentX) || definitionDomain.includes(previousX)) &&
(destinationDomain.includes(currentY) || destinationDomain.includes(previousY))) {
if(drawDash)
canvas.drawDashedLine(ctx, canvas.x2px(previousX), canvas.y2px(previousY), canvas.x2px(currentX), canvas.y2px(currentY))
canvas.drawDashedLine(canvas.x2px(previousX), canvas.y2px(previousY), canvas.x2px(currentX), canvas.y2px(currentY))
if(drawPoints) {
ctx.fillRect(canvas.x2px(previousX)-5, canvas.y2px(previousY)-1, 10, 2)
ctx.fillRect(canvas.x2px(previousX)-1, canvas.y2px(previousY)-5, 2, 10)
canvas.fillRect(canvas.x2px(previousX)-5, canvas.y2px(previousY)-1, 10, 2)
canvas.fillRect(canvas.x2px(previousX)-1, canvas.y2px(previousY)-5, 2, 10)
}
}
previousX = currentX
@ -143,21 +137,52 @@ class Function extends Common.ExecutableObject {
}
if(drawPoints) {
// Drawing the last cross
ctx.fillRect(canvas.x2px(previousX)-5, canvas.y2px(previousY)-1, 10, 2)
ctx.fillRect(canvas.x2px(previousX)-1, canvas.y2px(previousY)-5, 2, 10)
canvas.fillRect(canvas.x2px(previousX)-5, canvas.y2px(previousY)-1, 10, 2)
canvas.fillRect(canvas.x2px(previousX)-1, canvas.y2px(previousY)-5, 2, 10)
}
} else {
previousY = expr.execute(previousX)
for(var px = pxprecision; px < canvas.canvasSize.width; px += pxprecision) {
var currentX = canvas.px2x(px)
var currentY = expr.execute(currentX)
if((definitionDomain.includes(currentX) || definitionDomain.includes(previousX)) &&
(destinationDomain.includes(currentY) || destinationDomain.includes(previousY)) &&
Math.abs(previousY-currentY)<100) {
canvas.drawLine(ctx, canvas.x2px(previousX), canvas.y2px(previousY), canvas.x2px(currentX), canvas.y2px(currentY))
// Use max precision if function is trigonometrical on log scale.
let exprString = expr.expr
if(exprString.includes("sin") || exprString.includes("cos") || exprString.includes("tan"))
pxprecision = (canvas.logscalex || exprString.includes("tan")) ? 1 : 3
// Calculate the previousY at the start of the canvas
if(definitionDomain.includes(previousX))
previousY = expr.execute(previousX)
for(let px = pxprecision; px < canvas.width; px += pxprecision) {
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))
}
}
previousX = currentX
previousY = currentY
previousY = currentY
} else {
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.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,19 +16,19 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.pragma library
import { parseDomain, executeExpression, Expression, EmptySet, Domain } from "../mathlib.mjs"
import * as P from "../parameters.mjs"
import Objects from "../objects.mjs"
import Latex from "../math/latex.mjs"
.import "common.js" as Common
.import "function.js" as F
.import "../objects.js" as Objects
.import "../utils.js" as Utils
.import "../mathlib.js" as MathLib
.import "../historylib.js" as HistoryLib
.import "../parameters.js" as P
.import "../math/latex.js" as Latex
import { API as Common, ExecutableObject } from "common.mjs"
import Function from "function.mjs"
import { API as HistoryAPI } from "../history/common.mjs"
import { CreateNewObject } from "../historylib.mjs"
class GainBode extends Common.ExecutableObject {
export default class GainBode extends ExecutableObject {
static type(){return 'Gain Bode'}
static displayType(){return qsTr('Bode Magnitude')}
static displayTypeMultiple(){return qsTr('Bode Magnitudes')}
@ -40,11 +40,11 @@ class GainBode extends Common.ExecutableObject {
[QT_TRANSLATE_NOOP('prop','labelX')]: 'number',
[QT_TRANSLATE_NOOP('prop','omGraduation')]: 'boolean'
}}
constructor(name = null, visible = true, color = null, labelContent = 'name + value',
om_0 = '', pass = 'high', gain = '20', labelPosition = 'above', labelX = 1, omGraduation = false) {
if(name == null) name = Common.getNewName('G')
if(name == 'G') name = 'G₀' // G is reserved for sum of BODE magnitudes (Somme gains Bode).
if(name === 'G') name = 'G₀' // G is reserved for sum of BODE magnitudes (Somme gains Bode).
super(name, visible, color, labelContent)
if(typeof om_0 == "string") {
// Point name or create one
@ -52,15 +52,16 @@ class GainBode extends Common.ExecutableObject {
if(om_0 == null) {
// Create new point
om_0 = Objects.createNewRegisteredObject('Point', [Common.getNewName('ω'), true, this.color, 'name'])
HistoryLib.history.addToHistory(new HistoryLib.CreateNewObject(om_0.name, 'Point', om_0.export()))
HistoryAPI.addToHistory(new CreateNewObject(om_0.name, 'Point', om_0.export()))
om_0.update()
labelPosition = 'below'
}
om_0.requiredBy.push(this)
}
/** @type {Point} */
this.om_0 = om_0
this.pass = pass
if(typeof gain == 'number' || typeof gain == 'string') gain = new MathLib.Expression(gain.toString())
if(typeof gain == 'number' || typeof gain == 'string') gain = new Expression(gain.toString())
this.gain = gain
this.labelPosition = labelPosition
this.labelX = labelX
@ -68,27 +69,22 @@ class GainBode extends Common.ExecutableObject {
}
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`
}
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}
\\textsf{${pass}};${Latex.variable(this.om_0.name)} = ${this.om_0.x.latexMarkup} \\\\
${this.gain.latexMarkup}\\textsf{ dB/dec}
\\end{array}`
}
export() {
return [this.name, this.visible, this.color.toString(), this.labelContent,
this.om_0.name, this.pass.toString(), this.gain.toEditableString(), this.labelPosition, this.labelX, this.omGraduation]
}
execute(x=1) {
if(typeof x == 'string') x = MathLib.executeExpression(x)
if((this.pass == 'high' && x < this.om_0.x) || (this.pass == 'low' && x > this.om_0.x)) {
var dbfn = new MathLib.Expression(`${this.gain.execute()}*(ln(x)-ln(${this.om_0.x}))/ln(10)+${this.om_0.y}`)
if(typeof x == 'string') x = executeExpression(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}`)
return dbfn.execute(x)
} else {
return this.om_0.y.execute()
@ -96,10 +92,10 @@ class GainBode extends Common.ExecutableObject {
}
simplify(x = 1) {
var xval = x
if(typeof x == 'string') xval = MathLib.executeExpression(x)
if((this.pass == 'high' && xval < this.om_0.x) || (this.pass == 'low' && xval > this.om_0.x)) {
var dbfn = new MathLib.Expression(`${this.gain.execute()}*(ln(x)-ln(${this.om_0.x}))/ln(10)+${this.om_0.y}`)
let xval = x
if(typeof x == 'string') xval = executeExpression(x)
if((this.pass === 'high' && xval < this.om_0.x.execute()) || (this.pass === 'low' && xval > this.om_0.x.execute())) {
let dbfn = new Expression(`${this.gain.execute()}*(ln(x)-ln(${this.om_0.x}))/ln(10)+${this.om_0.y}`)
return dbfn.simplify(x)
} else {
return this.om_0.y.toString()
@ -110,34 +106,34 @@ class GainBode extends Common.ExecutableObject {
return true
}
draw(canvas, ctx) {
var base = [canvas.x2px(this.om_0.x), canvas.y2px(this.om_0.y)]
var dbfn = new MathLib.Expression(`${this.gain.execute()}*(log10(x)-log10(${this.om_0.x}))+${this.om_0.y}`)
var inDrawDom = new MathLib.EmptySet()
draw(canvas) {
let base = [canvas.x2px(this.om_0.x.execute()), canvas.y2px(this.om_0.y.execute())]
let dbfn = new Expression(`${this.gain.execute()}*(log10(x)-log10(${this.om_0.x}))+${this.om_0.y}`)
let inDrawDom = new EmptySet()
if(this.pass == 'high') {
// High pass, linear line from begining, then constant to the end.
canvas.drawLine(ctx, base[0], base[1], canvas.canvasSize.width, base[1])
inDrawDom = MathLib.parseDomain(`]-inf;${this.om_0.x}[`)
if(this.pass === 'high') {
// High pass, linear line from beginning, then constant to the end.
canvas.drawLine(base[0], base[1], canvas.width, base[1])
inDrawDom = parseDomain(`]-inf;${this.om_0.x}[`)
} else {
// Low pass, constant from the beginning, linear line to the end.
canvas.drawLine(ctx, base[0], base[1], 0, base[1])
inDrawDom = MathLib.parseDomain(`]${this.om_0.x};+inf[`)
canvas.drawLine(base[0], base[1], 0, base[1])
inDrawDom = parseDomain(`]${this.om_0.x};+inf[`)
}
F.Function.drawFunction(canvas, ctx, dbfn, inDrawDom, MathLib.Domain.R)
Function.drawFunction(canvas, dbfn, inDrawDom, Domain.R)
// Dashed line representing break in function
var xpos = canvas.x2px(this.om_0.x.execute())
var dashPxSize = 10
for(var i = 0; i < canvas.canvasSize.height && this.omGraduation; i += dashPxSize*2)
canvas.drawLine(ctx, xpos, i, xpos, i+dashPxSize)
let xpos = canvas.x2px(this.om_0.x.execute())
let dashPxSize = 10
for(let i = 0; i < canvas.height && this.omGraduation; i += dashPxSize*2)
canvas.drawLine(xpos, i, xpos, i+dashPxSize)
// Label
this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))
this.drawLabel(canvas, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))
}
update() {
super.update()
if(Objects.currentObjects['Somme gains Bode'] != undefined && Objects.currentObjects['Somme gains Bode'].length > 0) {
if(Objects.currentObjects['Somme gains Bode'] !== undefined && Objects.currentObjects['Somme gains Bode'].length > 0) {
Objects.currentObjects['Somme gains Bode'][0].recalculateCache()
} else {
Objects.createNewRegisteredObject('Somme gains Bode')

View file

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

View file

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

View file

@ -1,6 +1,6 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,45 +16,44 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.pragma library
.import "common.js" as Common
.import "../parameters.js" as P
.import "../math/latex.js" as Latex
import { API as Common, ExecutableObject } from "common.mjs"
import * as P from "../parameters.mjs"
import Latex from "../math/latex.mjs"
class RepartitionFunction extends Common.ExecutableObject {
export default class RepartitionFunction extends ExecutableObject {
static type(){return 'Repartition'}
static displayType(){return qsTr('Repartition')}
static displayTypeMultiple(){return qsTr('Repartition functions')}
static properties() {return {
[QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position,
[QT_TRANSLATE_NOOP('prop','labelX')]: 'number',
'comment1': QT_TRANSLATE_NOOP(
'comment',
'Note: Specify the probability for each value.'
),
[QT_TRANSLATE_NOOP('prop','probabilities')]: new P.Dictionary('string', 'float', /^-?[\d.,]+$/, /^-?[\d\.,]+$/, 'P({name_} = ', ') = '),
[QT_TRANSLATE_NOOP('prop','probabilities')]: new P.Dictionary('string', 'double', /^-?[\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',
beginIncluded = true, drawLineEnds = true, probabilities = {'0': '0'}, labelPosition = 'above', labelX = 1) {
probabilities = {'0': '0'}, labelPosition = 'above', labelX = 1) {
if(name == null) name = Common.getNewName('XYZUVW', "F_")
super(name, visible, color, labelContent)
this.beginIncluded = beginIncluded
this.drawLineEnds = drawLineEnds
this.probabilities = probabilities
this.labelPosition = labelPosition
this.labelX = labelX
this.update()
}
export() {
return [this.name, this.visible, this.color.toString(), this.labelContent,
this.beginIncluded, this.drawLineEnds, this.probabilities, this.labelPosition, this.labelX]
}
getReadableString() {
let keys = Object.keys(this.probabilities).sort((a,b) => a-b);
let varname = this.name.substring(this.name.indexOf("_")+1)
@ -69,7 +68,7 @@ class RepartitionFunction extends Common.ExecutableObject {
}
execute(x = 1) {
var ret = 0;
let ret = 0;
Object.keys(this.probabilities).sort((a,b) => a-b).forEach(idx => {
if(x >= idx) ret += parseFloat(this.probabilities[idx].replace(/,/g, '.'))
})
@ -77,6 +76,7 @@ class RepartitionFunction extends Common.ExecutableObject {
}
canExecute(x = 1) {return true}
// Simplify returns the simplified string of the expression.
simplify(x = 1) {
return this.execute(x)
@ -93,61 +93,51 @@ class RepartitionFunction extends Common.ExecutableObject {
}
}
draw(canvas, ctx) {
var currentY = 0;
var keys = Object.keys(this.probabilities).map(idx => parseInt(idx)).sort((a,b) => a-b)
draw(canvas) {
let currentY = 0;
let keys = Object.keys(this.probabilities).map(idx => parseInt(idx)).sort((a,b) => a-b)
if(canvas.isVisible(keys[0],this.probabilities[keys[0]].replace(/,/g, '.'))) {
canvas.drawLine(ctx,
0,
canvas.y2px(0),
canvas.x2px(keys[0]),
canvas.y2px(0)
)
canvas.drawLine(0, canvas.y2px(0), canvas.x2px(keys[0]), canvas.y2px(0))
if(canvas.isVisible(keys[0],0)) {
ctx.beginPath();
ctx.arc(canvas.x2px(keys[0])+4,canvas.y2px(0), 4, Math.PI / 2, 3 * Math.PI / 2);
ctx.stroke();
canvas.arc(canvas.x2px(keys[0])+4,canvas.y2px(0), 4, Math.PI / 2, 3 * Math.PI / 2);
}
}
for(var i = 0; i < keys.length-1; i++) {
var idx = keys[i];
for(let i = 0; i < keys.length-1; i++) {
let idx = keys[i];
currentY += parseFloat(this.probabilities[idx].replace(/,/g, '.'));
if(canvas.isVisible(idx,currentY) || canvas.isVisible(keys[i+1],currentY)) {
canvas.drawLine(ctx,
canvas.drawLine(
Math.max(0,canvas.x2px(idx)),
canvas.y2px(currentY),
Math.min(canvas.canvasSize.width,canvas.x2px(keys[i+1])),
Math.min(canvas.width,canvas.x2px(keys[i+1])),
canvas.y2px(currentY)
)
if(canvas.isVisible(idx,currentY)) {
ctx.beginPath();
ctx.arc(canvas.x2px(idx),canvas.y2px(currentY), 4, 0, 2 * Math.PI);
ctx.fill();
canvas.disc(canvas.x2px(idx), canvas.y2px(currentY), 4)
}
if(canvas.isVisible(keys[i+1],currentY)) {
ctx.beginPath();
ctx.arc(canvas.x2px(keys[i+1])+4,canvas.y2px(currentY), 4, Math.PI / 2, 3 * Math.PI / 2);
ctx.stroke();
canvas.arc(canvas.x2px(keys[i+1])+4,canvas.y2px(currentY), 4, Math.PI / 2, 3 * Math.PI / 2);
}
}
}
if(canvas.isVisible(keys[keys.length-1],currentY+parseFloat(this.probabilities[keys[keys.length-1]]))) {
canvas.drawLine(ctx,
canvas.drawLine(
Math.max(0,canvas.x2px(keys[keys.length-1])),
canvas.y2px(currentY+parseFloat(this.probabilities[keys[keys.length-1]].replace(/,/g, '.'))),
canvas.canvasSize.width,
canvas.width,
canvas.y2px(currentY+parseFloat(this.probabilities[keys[keys.length-1]].replace(/,/g, '.')))
)
ctx.beginPath();
ctx.arc(
canvas.disc(
canvas.x2px(keys[keys.length-1]),
canvas.y2px(currentY+parseFloat(this.probabilities[keys[keys.length-1]].replace(/,/g, '.'))),
4, 0, 2 * Math.PI);
ctx.fill();
canvas.y2px(
currentY+parseFloat(this.probabilities[keys[keys.length-1]].replace(/,/g, '.'))
),
4
)
}
// Label
this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))
this.drawLabel(canvas, 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.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,16 +16,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.pragma library
import { Sequence as MathSequence, Domain } from "../mathlib.mjs"
import * as P from "../parameters.mjs"
import Latex from "../math/latex.mjs"
.import "common.js" as Common
.import "function.js" as F
.import "../mathlib.js" as MathLib
.import "../parameters.js" as P
.import "../math/latex.js" as Latex
import { API as Common, ExecutableObject } from "common.mjs"
import Function from "function.mjs"
class Sequence extends Common.ExecutableObject {
export default class Sequence extends ExecutableObject {
static type(){return 'Sequence'}
static displayType(){return qsTr('Sequence')}
static displayTypeMultiple(){return qsTr('Sequences')}
@ -56,21 +55,17 @@ class Sequence extends Common.ExecutableObject {
this.labelX = labelX
this.update()
}
export() {
return [this.name, this.visible, this.color.toString(), this.labelContent,
this.drawPoints, this.drawDashedLines, this.defaultExpression, this.baseValues, this.labelPosition, this.labelX]
}
update() {
console.log('Updating sequence', this.sequence)
super.update()
if(
this.sequence == null || this.baseValues != this.sequence.baseValues ||
this.sequence.name != this.name ||
this.sequence.expr != Object.values(this.defaultExpression)[0] ||
this.sequence.valuePlus != Object.keys(this.defaultExpression)[0]
this.sequence == null || this.baseValues !== this.sequence.baseValues ||
this.sequence.name !== this.name ||
this.sequence.expr !== Object.values(this.defaultExpression)[0] ||
this.sequence.valuePlus !== Object.keys(this.defaultExpression)[0]
)
this.sequence = new MathLib.Sequence(
this.sequence = new MathSequence(
this.name, this.baseValues,
Object.keys(this.defaultExpression)[0],
Object.values(this.defaultExpression)[0]
@ -86,15 +81,15 @@ class Sequence extends Common.ExecutableObject {
}
execute(x = 1) {
if(x % 1 == 0)
if(x % 1 === 0)
return this.sequence.execute(x)
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(x = 1) {
if(x % 1 == 0)
if(x % 1 === 0)
return this.sequence.simplify(x)
return null
}
@ -121,11 +116,11 @@ class Sequence extends Common.ExecutableObject {
}
}
draw(canvas, ctx) {
F.Function.drawFunction(canvas, ctx, this.sequence, canvas.logscalex ? MathLib.Domain.NE : MathLib.Domain.N, MathLib.Domain.R, this.drawPoints, this.drawDashedLines)
draw(canvas) {
Function.drawFunction(canvas, this.sequence, canvas.logscalex ? Domain.NE : Domain.N, Domain.R, this.drawPoints, this.drawDashedLines)
// Label
this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))
this.drawLabel(canvas, 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.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,17 +16,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.pragma library
import { parseDomain, Expression, Domain } from "../mathlib.mjs"
import * as P from "../parameters.mjs"
import Objects from "../objects.mjs"
import Latex from "../math/latex.mjs"
.import "common.js" as Common
.import "function.js" as F
.import "../objects.js" as Objects
.import "../mathlib.js" as MathLib
.import "../parameters.js" as P
.import "../math/latex.js" as Latex
import { ExecutableObject } from "common.mjs"
import Function from "function.mjs"
class SommeGainsBode extends Common.ExecutableObject {
export default class SommeGainsBode extends ExecutableObject {
static type(){return 'Somme gains Bode'}
static displayType(){return qsTr('Bode Magnitudes Sum')}
static displayTypeMultiple(){return qsTr('Bode Magnitudes Sum')}
@ -45,10 +44,6 @@ class SommeGainsBode extends Common.ExecutableObject {
this.recalculateCache()
}
export() {
return [this.name, this.visible, this.color.toString(), this.labelContent, this.labelPosition, this.labelX]
}
getReadableString() {
return `${this.name} = ${Objects.getObjectsName('Gain Bode').join(' + ')}`
}
@ -58,7 +53,7 @@ class SommeGainsBode extends Common.ExecutableObject {
}
execute(x = 0) {
for(var [dbfn, inDrawDom] of this.cachedParts) {
for(let [dbfn, inDrawDom] of this.cachedParts) {
if(inDrawDom.includes(x)) {
return dbfn.execute(x)
}
@ -71,7 +66,7 @@ class SommeGainsBode extends Common.ExecutableObject {
}
simplify(x = 1) {
for(var [dbfn, inDrawDom] of this.cachedParts) {
for(let [dbfn, inDrawDom] of this.cachedParts) {
if(inDrawDom.includes(x)) {
return dbfn.simplify(x)
}
@ -82,33 +77,33 @@ class SommeGainsBode extends Common.ExecutableObject {
recalculateCache() {
this.cachedParts = []
// Calculating this is fairly resource expansive so it's cached.
if(Objects.currentObjects['Gain Bode'] != undefined) {
if(Objects.currentObjects['Gain Bode'] !== undefined) {
console.log('Recalculating cache gain')
// Minimum to draw (can be expended if needed, just not infinite or it'll cause issues.
var drawMin = 0.001
let drawMin = 0.001
var baseY = 0
var om0xGains = {1000000000: 0} // To draw the last part
var om0xPass = {1000000000: 'high'} // To draw the last part
Objects.currentObjects['Gain Bode'].forEach(function(gainObj) { // Sorting by their om_0 position.
var om0x = gainObj.om_0.x.execute()
if(om0xGains[om0x] == undefined) {
let baseY = 0
let om0xGains = {1000000000: 0} // To draw the last part
let om0xPass = {1000000000: 'high'} // To draw the last part
for(/** @type {GainBode} */ let gainObj of Objects.currentObjects['Gain Bode']) { // Sorting by their om_0 position.
let om0x = gainObj.om_0.x.execute()
if(om0xGains[om0x] === undefined) {
om0xGains[om0x] = gainObj.gain.execute()
om0xPass[om0x] = gainObj.pass == 'high'
om0xPass[om0x] = gainObj.pass === 'high'
} else {
om0xGains[om0x+0.001] = gainObj.gain.execute()
om0xPass[om0x+0.001] = gainObj.pass == 'high'
om0xPass[om0x+0.001] = gainObj.pass === 'high'
}
baseY += gainObj.execute(drawMin)
})
}
// Sorting the om_0x
var om0xList = Object.keys(om0xGains).map(x => parseFloat(x)) // THEY WERE CONVERTED TO STRINGS...
let om0xList = Object.keys(om0xGains).map(x => parseFloat(x)) // THEY WERE CONVERTED TO STRINGS...
om0xList.sort((a,b) => a - b)
// Adding the total gains.
var gainsBeforeP = []
var gainsAfterP = []
var gainTotal = 0
for(var om0x of om0xList){
let gainsBeforeP = []
let gainsAfterP = []
let gainTotal = 0
for(let om0x of om0xList){
if(om0xPass[om0x]) { // High-pass
gainsBeforeP.push(om0xGains[om0x])
gainsAfterP.push(0)
@ -119,10 +114,10 @@ class SommeGainsBode extends Common.ExecutableObject {
}
}
// Calculating parts
var previousPallier = drawMin
for(var pallier = 0; pallier < om0xList.length; pallier++) {
var dbfn = new MathLib.Expression(`${gainTotal}*(ln(x)-ln(${previousPallier}))/ln(10)+${baseY}`)
var inDrawDom = MathLib.parseDomain(`]${previousPallier};${om0xList[pallier]}]`)
let previousPallier = drawMin
for(let pallier = 0; pallier < om0xList.length; pallier++) {
let dbfn = new Expression(`${gainTotal}*(ln(x)-ln(${previousPallier}))/ln(10)+${baseY}`)
let inDrawDom = parseDomain(`]${previousPallier};${om0xList[pallier]}]`)
this.cachedParts.push([dbfn, inDrawDom])
previousPallier = om0xList[pallier]
baseY = dbfn.execute(om0xList[pallier])
@ -131,13 +126,13 @@ class SommeGainsBode extends Common.ExecutableObject {
}
}
draw(canvas, ctx) {
draw(canvas) {
if(this.cachedParts.length > 0) {
for(var [dbfn, inDrawDom] of this.cachedParts) {
F.Function.drawFunction(canvas, ctx, dbfn, inDrawDom, MathLib.Domain.R)
for(let [dbfn, inDrawDom] of this.cachedParts) {
Function.drawFunction(canvas, dbfn, inDrawDom, Domain.R)
if(inDrawDom.includes(this.labelX)) {
// Label
this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))
this.drawLabel(canvas, 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.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,16 +16,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.pragma library
import { executeExpression, Expression } from "../mathlib.mjs"
import * as P from "../parameters.mjs"
import Objects from "../objects.mjs"
import Latex from "../math/latex.mjs"
.import "common.js" as Common
.import "../objects.js" as Objects
.import "../mathlib.js" as MathLib
.import "../parameters.js" as P
.import "../math/latex.js" as Latex
import { ExecutableObject } from "common.mjs"
class SommePhasesBode extends Common.ExecutableObject {
export default class SommePhasesBode extends ExecutableObject {
static type(){return 'Somme phases Bode'}
static displayType(){return qsTr('Bode Phases Sum')}
static displayTypeMultiple(){return qsTr('Bode Phases Sum')}
@ -44,10 +42,6 @@ class SommePhasesBode extends Common.ExecutableObject {
this.recalculateCache()
}
export() {
return [this.name, this.visible, this.color.toString(), this.labelContent, this.labelPosition, this.labelX]
}
getReadableString() {
return `${this.name} = ${Objects.getObjectsName('Phase Bode').join(' + ')}`
}
@ -57,18 +51,18 @@ class SommePhasesBode extends Common.ExecutableObject {
}
execute(x=1) {
if(typeof x == 'string') x = MathLib.executeExpression(x)
for(var i = 0; i < this.om0xList.length-1; i++) {
if(typeof x == 'string') x = executeExpression(x)
for(let i = 0; i < this.om0xList.length-1; i++) {
if(x >= this.om0xList[i] && x < this.om0xList[i+1]) return this.phasesList[i]
}
}
simplify(x = 1) {
var xval = x
if(typeof x == 'string') xval = MathLib.executeExpression(x)
for(var i = 0; i < this.om0xList.length-1; i++) {
let xval = x
if(typeof x == 'string') xval = executeExpression(x)
for(let i = 0; i < this.om0xList.length-1; i++) {
if(xval >= this.om0xList[i] && xval < this.om0xList[i+1]) {
return (new MathLib.Expression(this.phasesExprList[i])).simplify()
return (new Expression(this.phasesExprList[i])).simplify()
}
}
return '0'
@ -80,20 +74,20 @@ class SommePhasesBode extends Common.ExecutableObject {
recalculateCache() {
// Minimum to draw (can be expended if needed, just not infinite or it'll cause issues.
var drawMin = 0.001
var drawMax = 100000
let drawMin = 0.001
let drawMax = 100000
this.om0xList = [drawMin, drawMax]
this.phasesList = [0]
this.phasesExprList = ['0']
var phasesDict = {}
var phasesExprDict = {}
let phasesDict = {}
let phasesExprDict = {}
phasesDict[drawMax] = 0
if(Objects.currentObjects['Phase Bode'] != undefined) {
if(Objects.currentObjects['Phase Bode'] !== undefined) {
console.log('Recalculating cache phase')
for(var obj of Objects.currentObjects['Phase Bode']) {
for(/** @type {PhaseBode} */ let obj of Objects.currentObjects['Phase Bode']) {
this.om0xList.push(obj.om_0.x.execute())
if(phasesDict[obj.om_0.x.execute()] == undefined) {
if(phasesDict[obj.om_0.x.execute()] === undefined) {
phasesDict[obj.om_0.x.execute()] = obj.phase.execute()
phasesExprDict[obj.om_0.x.execute()] = obj.phase.toEditableString()
} else {
@ -104,26 +98,26 @@ class SommePhasesBode extends Common.ExecutableObject {
this.phasesExprList[0] += '+' + obj.om_0.y.toEditableString()
}
this.om0xList.sort((a,b) => a - b)
var totalAdded = this.phasesList[0]
for(var i = 1; i < this.om0xList.length; i++) {
let totalAdded = this.phasesList[0]
for(let i = 1; i < this.om0xList.length; i++) {
this.phasesList[i] = this.phasesList[i-1] + phasesDict[this.om0xList[i]]
this.phasesExprList[i] = this.phasesExprList[i-1] + '+' + phasesDict[this.om0xList[i]]
}
}
}
draw(canvas, ctx) {
for(var i = 0; i < this.om0xList.length-1; i++) {
var om0xBegin = canvas.x2px(this.om0xList[i])
var om0xEnd = canvas.x2px(this.om0xList[i+1])
var baseY = canvas.y2px(this.phasesList[i])
var nextY = canvas.y2px(this.phasesList[i+1])
canvas.drawLine(ctx, om0xBegin, baseY, om0xEnd, baseY)
canvas.drawLine(ctx, om0xEnd, baseY, om0xEnd, nextY)
draw(canvas) {
for(let i = 0; i < this.om0xList.length-1; i++) {
let om0xBegin = canvas.x2px(this.om0xList[i])
let om0xEnd = canvas.x2px(this.om0xList[i+1])
let baseY = canvas.y2px(this.phasesList[i])
let nextY = canvas.y2px(this.phasesList[i+1])
canvas.drawLine(om0xBegin, baseY, om0xEnd, baseY)
canvas.drawLine(om0xEnd, baseY, om0xEnd, nextY)
}
// Label
this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))
this.drawLabel(canvas, 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.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,16 +16,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.pragma library
import { Expression } from "../mathlib.mjs"
import * as P from "../parameters.mjs"
import Latex from "../math/latex.mjs"
.import "common.js" as Common
.import "../mathlib.js" as MathLib
.import "../parameters.js" as P
.import "../math/latex.js" as Latex
import { API as Common, DrawableObject } from "common.mjs"
class Text extends Common.DrawableObject {
export default class Text extends DrawableObject {
static type(){return 'Text'}
static displayType(){return qsTr('Text')}
static displayTypeMultiple(){return qsTr('Texts')}
@ -34,7 +32,7 @@ class Text extends Common.DrawableObject {
[QT_TRANSLATE_NOOP('prop','y')]: new P.Expression(),
[QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Positioning,
[QT_TRANSLATE_NOOP('prop','text')]: 'string',
'comment1': QT_TRANSLATE_NOOP(
'comment1': QT_TRANSLATE_NOOP(
'comment',
'If you have latex enabled, you can use use latex markup in between $$ to create equations.'
),
@ -45,9 +43,9 @@ class Text extends Common.DrawableObject {
x = 1, y = 0, labelPosition = 'center', text = 'New text', disableLatex = false) {
if(name == null) name = Common.getNewName('t')
super(name, visible, color, labelContent)
if(typeof x == 'number' || typeof x == 'string') x = new MathLib.Expression(x.toString())
if(typeof x == 'number' || typeof x == 'string') x = new Expression(x.toString())
this.x = x
if(typeof y == 'number' || typeof y == 'string') y = new MathLib.Expression(y.toString())
if(typeof y == 'number' || typeof y == 'string') y = new Expression(y.toString())
this.y = y
this.labelPosition = labelPosition
this.text = text
@ -80,10 +78,6 @@ class Text extends Common.DrawableObject {
return `${Latex.variable(this.name)} = "\\textsf{${this.latexMarkupText()}}"`
}
export() {
return [this.name, this.visible, this.color.toString(), this.labelContent, this.x.toEditableString(), this.y.toEditableString(), this.labelPosition, this.text, this.disableLatex]
}
getLabel() {
return this.text
}
@ -92,9 +86,9 @@ class Text extends Common.DrawableObject {
return `\\textsf{${this.latexMarkupText()}}`
}
draw(canvas, ctx) {
draw(canvas) {
let yOffset = this.disableLatex ? canvas.textsize-4 : 0
this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.x.execute()), canvas.y2px(this.y.execute())+yOffset, this.disableLatex)
this.drawLabel(canvas, this.labelPosition, canvas.x2px(this.x.execute()), canvas.y2px(this.y.execute())+yOffset, this.disableLatex)
}
}

View file

@ -1,6 +1,6 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2023 Ad5001
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -16,23 +16,21 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.pragma library
import { Expression } from "../mathlib.mjs"
import * as P from "../parameters.mjs"
import Latex from "../math/latex.mjs"
import Objects from "../objects.mjs"
.import "common.js" as Common
.import "../objects.js" as Objects
.import "../mathlib.js" as MathLib
.import "../parameters.js" as P
.import "../math/latex.js" as Latex
import { API as Common, DrawableObject } from "common.mjs"
class XCursor extends Common.DrawableObject {
export default class XCursor extends DrawableObject {
static type(){return 'X Cursor'}
static displayType(){return qsTr('X Cursor')}
static displayTypeMultiple(){return qsTr('X Cursors')}
static properties() {return {
[QT_TRANSLATE_NOOP('prop','x')]: 'Expression',
[QT_TRANSLATE_NOOP('prop','targetElement')]: new P.ObjectType('ExecutableObject'),
[QT_TRANSLATE_NOOP('prop','x')]: new P.Expression(),
[QT_TRANSLATE_NOOP('prop','targetElement')]: new P.ObjectType('ExecutableObject', true),
[QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position,
[QT_TRANSLATE_NOOP('prop','approximate')]: 'boolean',
[QT_TRANSLATE_NOOP('prop','rounding')]: 'number',
@ -51,7 +49,7 @@ class XCursor extends Common.DrawableObject {
super(name, visible, color, labelContent)
this.approximate = approximate
this.rounding = rounding
if(typeof x == 'number' || typeof x == 'string') x = new MathLib.Expression(x.toString())
if(typeof x == 'number' || typeof x == 'string') x = new Expression(x.toString())
this.x = x
this.targetElement = targetElement
if(typeof targetElement == "string") {
@ -62,12 +60,6 @@ class XCursor extends Common.DrawableObject {
this.targetValuePosition = targetValuePosition
}
export() {
return [this.name, this.visible, this.color.toString(), this.labelContent,
this.x.toEditableString(), this.targetElement == null ? null : this.targetElement.name, this.labelPosition,
this.approximate, this.rounding, this.displayStyle, this.targetValuePosition]
}
getReadableString() {
if(this.targetElement == null) return `${this.name} = ${this.x.toString()}`
return `${this.name} = ${this.x.toString()}\n${this.getTargetValueLabel()}`
@ -93,8 +85,8 @@ class XCursor extends Common.DrawableObject {
}
getTargetValueLatexLabel() {
var t = this.targetElement
var approx = ''
let t = this.targetElement
let approx = ''
if(this.approximate) {
approx = t.execute(this.x.execute())
approx = approx.toPrecision(this.rounding + Math.round(approx).toString().length)
@ -144,39 +136,35 @@ class XCursor extends Common.DrawableObject {
}
}
draw(canvas, ctx) {
draw(canvas) {
let xpos = canvas.x2px(this.x.execute())
switch(this.displayStyle) {
case '— — — — — — —':
var dashPxSize = 10
for(var i = 0; i < canvas.canvasSize.height; i += dashPxSize*2)
canvas.drawLine(ctx, xpos, i, xpos, i+dashPxSize)
canvas.drawDashedLine(xpos, 0, xpos, canvas.height, 20)
break;
case '⸺⸺⸺⸺⸺⸺':
canvas.drawXLine(ctx, this.x.execute())
canvas.drawXLine(this.x.execute())
break;
case '• • • • • • • • • •':
var pointDistancePx = 10
var pointSize = 2
ctx.beginPath();
for(var i = 0; i < canvas.canvasSize.height; i += pointDistancePx)
ctx.ellipse(xpos-pointSize/2, i-pointSize/2, pointSize, pointSize)
ctx.fill();
let pointDistancePx = 10
let pointSize = 2
for(let i = 0; i < canvas.height; i += pointDistancePx)
canvas.disc(xpos, i, pointSize)
break;
}
// Drawing label at the top of the canvas.
this.drawLabel(canvas, ctx, this.labelPosition, xpos, 0, false, null, null,
(x,y,ltxImg) => canvas.drawVisibleImage(ctx, ltxImg.source, x, 5, ltxImg.width, ltxImg.height),
(x,y,text,textSize) => canvas.drawVisibleText(ctx, text, x, textSize.height+5))
this.drawLabel(canvas, this.labelPosition, xpos, 0, false, null, null,
(x,y,ltxImg) => canvas.drawVisibleImage(ltxImg.source, x, 5, ltxImg.width, ltxImg.height),
(x,y,text,textSize) => canvas.drawVisibleText(text, x, textSize.height+5))
// Drawing label at the position of the target element.
if(this.targetValuePosition == 'Next to target' && this.targetElement != null) {
if(this.targetValuePosition === 'Next to target' && this.targetElement != null) {
let ypos = canvas.y2px(this.targetElement.execute(this.x.execute()))
this.drawLabel(canvas, ctx, this.labelPosition, xpos, ypos, false,
this.drawLabel(canvas, this.labelPosition, xpos, ypos, false,
this.getTargetValueLatexLabel.bind(this), this.getTargetValueLabel.bind(this),
(x,y,ltxImg) => canvas.drawVisibleImage(ctx, ltxImg.source, x, y, ltxImg.width, ltxImg.height),
(x,y,text,textSize) => canvas.drawVisibleText(ctx, text, x, y+textSize.height))
(x,y,ltxImg) => canvas.drawVisibleImage(ltxImg.source, x, y, ltxImg.width, ltxImg.height),
(x,y,text,textSize) => canvas.drawVisibleText(text, x, y+textSize.height))
}
}
}

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