Reorganizing paths
This commit is contained in:
parent
e9d204daab
commit
34cb856dd4
249 changed files with 118 additions and 294 deletions
57
common/src/objs/autoload.mjs
Normal file
57
common/src/objs/autoload.mjs
Normal file
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* 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 Objects from "../module/objects.mjs"
|
||||
import { DrawableObject } from "./common.mjs"
|
||||
import Point from "./point.mjs"
|
||||
import Text from "./text.mjs"
|
||||
import Function from "./function.mjs"
|
||||
import BodeMagnitude from "./bodemagnitude.mjs"
|
||||
import BodePhase from "./bodephase.mjs"
|
||||
import BodeMagnitudeSum from "./bodemagnitudesum.mjs"
|
||||
import BodePhaseSum from "./bodephasesum.mjs"
|
||||
import XCursor from "./xcursor.mjs"
|
||||
import Sequence from "./sequence.mjs"
|
||||
import DistributionFunction from "./distribution.mjs"
|
||||
|
||||
/**
|
||||
* Registers the object obj in the object list.
|
||||
* @param {DrawableObject} obj - Object to be registered.
|
||||
*/
|
||||
function registerObject(obj) {
|
||||
// Registers an object to be used in LogarithmPlotter.
|
||||
if(obj.prototype instanceof DrawableObject) {
|
||||
if(!Objects.types[obj.type()])
|
||||
Objects.types[obj.type()] = obj
|
||||
} else {
|
||||
console.error("Could not register object " + (obj?.type() ?? obj.constructor.name) + ", as it isn't a DrawableObject.")
|
||||
}
|
||||
}
|
||||
|
||||
if(Object.keys(Objects.types).length === 0) {
|
||||
registerObject(Point)
|
||||
registerObject(Text)
|
||||
registerObject(Function)
|
||||
registerObject(BodeMagnitude)
|
||||
registerObject(BodePhase)
|
||||
registerObject(BodeMagnitudeSum)
|
||||
registerObject(BodePhaseSum)
|
||||
registerObject(XCursor)
|
||||
registerObject(Sequence)
|
||||
registerObject(DistributionFunction)
|
||||
}
|
162
common/src/objs/bodemagnitude.mjs
Normal file
162
common/src/objs/bodemagnitude.mjs
Normal file
|
@ -0,0 +1,162 @@
|
|||
/**
|
||||
* 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 { parseDomain, executeExpression, Expression, EmptySet, Domain } from "../math/index.mjs"
|
||||
import { CreateNewObject } from "../history/index.mjs"
|
||||
import * as P from "../parameters.mjs"
|
||||
import Objects from "../module/objects.mjs"
|
||||
import Latex from "../module/latex.mjs"
|
||||
import History from "../module/history.mjs"
|
||||
|
||||
import { ExecutableObject } from "./common.mjs"
|
||||
import Function from "./function.mjs"
|
||||
|
||||
export default class BodeMagnitude extends ExecutableObject {
|
||||
static type() {
|
||||
return "Gain Bode"
|
||||
}
|
||||
|
||||
static displayType() {
|
||||
return qsTranslate("bodemagnitude", "Bode Magnitude")
|
||||
}
|
||||
|
||||
static displayTypeMultiple() {
|
||||
return qsTranslate("bodemagnitude", "Bode Magnitudes")
|
||||
}
|
||||
|
||||
static properties() {
|
||||
return {
|
||||
[QT_TRANSLATE_NOOP("prop", "om_0")]: new P.ObjectType("Point"),
|
||||
[QT_TRANSLATE_NOOP("prop", "pass")]: P.Enum.BodePass,
|
||||
[QT_TRANSLATE_NOOP("prop", "gain")]: new P.Expression(),
|
||||
[QT_TRANSLATE_NOOP("prop", "labelPosition")]: P.Enum.Position,
|
||||
[QT_TRANSLATE_NOOP("prop", "labelX")]: "number",
|
||||
[QT_TRANSLATE_NOOP("prop", "omGraduation")]: "boolean"
|
||||
}
|
||||
}
|
||||
|
||||
constructor(name = null, visible = true, color = null, labelContent = "name + value",
|
||||
om_0 = "", pass = "high", gain = "20", labelPosition = "above", labelX = 1, omGraduation = false) {
|
||||
if(name == null) name = Objects.getNewName("G")
|
||||
if(name === "G") name = "G₀" // G is reserved for sum of BODE magnitudes (Somme gains Bode).
|
||||
super(name, visible, color, labelContent)
|
||||
if(typeof om_0 == "string") {
|
||||
// Point name or create one
|
||||
om_0 = Objects.currentObjectsByName[om_0]
|
||||
if(om_0 == null) {
|
||||
// Create new point
|
||||
om_0 = Objects.createNewRegisteredObject("Point", [Objects.getNewName("ω"), true, this.color, "name"])
|
||||
History.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 Expression(gain.toString())
|
||||
this.gain = gain
|
||||
this.labelPosition = labelPosition
|
||||
this.labelX = labelX
|
||||
this.omGraduation = omGraduation
|
||||
}
|
||||
|
||||
getReadableString() {
|
||||
let pass = this.pass === "low" ? qsTranslate("bodemagnitude", "low-pass") : qsTranslate("bodemagnitude", "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" ? qsTranslate("bodemagnitude", "low-pass") : qsTranslate("bodemagnitude", "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}`
|
||||
}
|
||||
|
||||
execute(x = 1) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
simplify(x = 1) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
canExecute(x = 1) {
|
||||
return true
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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(base[0], base[1], 0, base[1])
|
||||
inDrawDom = parseDomain(`]${this.om_0.x};+inf[`)
|
||||
}
|
||||
Function.drawFunction(canvas, dbfn, inDrawDom, Domain.R)
|
||||
// Dashed line representing break in function
|
||||
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, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))
|
||||
}
|
||||
|
||||
update() {
|
||||
super.update()
|
||||
/** @type {BodeMagnitudeSum[]} */
|
||||
let sumObjs = Objects.currentObjects["Somme gains Bode"]
|
||||
if(sumObjs !== undefined && sumObjs.length > 0) {
|
||||
sumObjs[0].recalculateCache()
|
||||
} else {
|
||||
Objects.createNewRegisteredObject("Somme gains Bode")
|
||||
}
|
||||
}
|
||||
|
||||
delete() {
|
||||
super.delete()
|
||||
/** @type {BodeMagnitudeSum[]} */
|
||||
let sumObjs = Objects.currentObjects["Somme gains Bode"]
|
||||
if(sumObjs !== undefined && sumObjs.length > 0) {
|
||||
sumObjs[0].recalculateCache()
|
||||
}
|
||||
}
|
||||
}
|
158
common/src/objs/bodemagnitudesum.mjs
Normal file
158
common/src/objs/bodemagnitudesum.mjs
Normal file
|
@ -0,0 +1,158 @@
|
|||
/**
|
||||
* 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 { Range, Expression, Domain } from "../math/index.mjs"
|
||||
import * as P from "../parameters.mjs"
|
||||
import Objects from "../module/objects.mjs"
|
||||
import Latex from "../module/latex.mjs"
|
||||
|
||||
import { ExecutableObject } from "./common.mjs"
|
||||
import Function from "./function.mjs"
|
||||
|
||||
|
||||
export default class BodeMagnitudeSum extends ExecutableObject {
|
||||
static type() {
|
||||
return "Somme gains Bode"
|
||||
}
|
||||
|
||||
static displayType() {
|
||||
return qsTranslate("bodemagnitudesum", "Bode Magnitudes Sum")
|
||||
}
|
||||
|
||||
static displayTypeMultiple() {
|
||||
return qsTranslate("bodemagnitudesum", "Bode Magnitudes Sum")
|
||||
}
|
||||
|
||||
static createable() {
|
||||
return false
|
||||
}
|
||||
|
||||
static properties() {
|
||||
return {
|
||||
[QT_TRANSLATE_NOOP("prop", "labelPosition")]: P.Enum.Position,
|
||||
[QT_TRANSLATE_NOOP("prop", "labelX")]: "number"
|
||||
}
|
||||
}
|
||||
|
||||
constructor(name = null, visible = true, color = null, labelContent = "name + value",
|
||||
labelPosition = "above", labelX = 1) {
|
||||
if(name == null) name = "G"
|
||||
super(name, visible, color, labelContent)
|
||||
this.labelPosition = labelPosition
|
||||
this.labelX = labelX
|
||||
this.recalculateCache()
|
||||
}
|
||||
|
||||
getReadableString() {
|
||||
return `${this.name} = ${Objects.getObjectsName("Gain Bode").join(" + ")}`
|
||||
}
|
||||
|
||||
getLatexString() {
|
||||
return `${Latex.variable(this.name)} = ${Objects.getObjectsName("Gain Bode").map(name => Latex.variable(name)).join(" + ")}`
|
||||
}
|
||||
|
||||
execute(x = 0) {
|
||||
for(let [limitedDrawFunction, inDrawDom] of this.cachedParts) {
|
||||
if(inDrawDom.includes(x)) {
|
||||
return limitedDrawFunction.execute(x)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
canExecute(x = 1) {
|
||||
return true // Should always be true
|
||||
}
|
||||
|
||||
simplify(x = 1) {
|
||||
for(let [limitedDrawFunction, inDrawDom] of this.cachedParts) {
|
||||
if(inDrawDom.includes(x)) {
|
||||
return limitedDrawFunction.simplify(x)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
recalculateCache() {
|
||||
this.cachedParts = []
|
||||
// Calculating this is fairly resource expansive so it's cached.
|
||||
let magnitudeObjects = Objects.currentObjects["Gain Bode"]
|
||||
if(magnitudeObjects === undefined || magnitudeObjects.length < 1) {
|
||||
Objects.deleteObject(this.name)
|
||||
} else {
|
||||
console.log("Recalculating cache gain")
|
||||
// Minimum to draw (can be expended if needed, just not infinite or it'll cause issues.
|
||||
const MIN_DRAW = 1e-20
|
||||
// Format: [[x value of where the filter transitions, magnitude, high-pass (bool)]]
|
||||
const magnitudes = []
|
||||
const XVALUE = 0
|
||||
const MAGNITUDE = 1
|
||||
const PASS = 2
|
||||
magnitudes.push([Number.MAX_VALUE, 0, true]) // Draw the ending section
|
||||
// Collect data from current magnitude (or gain in French) objects.
|
||||
let baseY = 0
|
||||
for(/** @type {BodeMagnitude} */ let magnitudeObj of magnitudeObjects) { // Sorting by their om_0 position.
|
||||
const om0x = magnitudeObj.om_0.x.execute()
|
||||
magnitudes.push([om0x, magnitudeObj.gain.execute(), magnitudeObj.pass === "high"])
|
||||
baseY += magnitudeObj.execute(MIN_DRAW)
|
||||
}
|
||||
// Sorting the data by their x transitions value
|
||||
magnitudes.sort((a, b) => a[XVALUE] - b[XVALUE])
|
||||
// Adding the total gains.
|
||||
let magnitudesBeforeTransition = []
|
||||
let magnitudesAfterTransition = []
|
||||
let totalMagnitudeAtStart = 0 // Magnitude at the lowest x value (sum of all high-pass magnitudes)
|
||||
for(let [om0x, magnitude, highpass] of magnitudes) {
|
||||
if(highpass) {
|
||||
magnitudesBeforeTransition.push(magnitude)
|
||||
magnitudesAfterTransition.push(0)
|
||||
totalMagnitudeAtStart += magnitude
|
||||
} else {
|
||||
magnitudesBeforeTransition.push(0)
|
||||
magnitudesAfterTransition.push(magnitude)
|
||||
}
|
||||
}
|
||||
// Calculating parts
|
||||
let previousTransitionX = MIN_DRAW
|
||||
let currentMagnitude = totalMagnitudeAtStart
|
||||
for(let transitionID = 0; transitionID < magnitudes.length; transitionID++) {
|
||||
const transitionX = magnitudes[transitionID][XVALUE]
|
||||
// Create draw function that will be used during drawing.
|
||||
const limitedDrawFunction = new Expression(`${currentMagnitude}*(ln(x)-ln(${previousTransitionX}))/ln(10)+${baseY}`)
|
||||
const drawDomain = new Range(previousTransitionX, transitionX, true, false)
|
||||
this.cachedParts.push([limitedDrawFunction, drawDomain])
|
||||
// Prepare default values for next function.
|
||||
previousTransitionX = transitionX
|
||||
baseY = limitedDrawFunction.execute(transitionX)
|
||||
currentMagnitude += magnitudesAfterTransition[transitionID] - magnitudesBeforeTransition[transitionID]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
draw(canvas) {
|
||||
if(this.cachedParts.length > 0) {
|
||||
for(let [limitedDrawFunction, drawDomain] of this.cachedParts) {
|
||||
Function.drawFunction(canvas, limitedDrawFunction, drawDomain, Domain.R)
|
||||
// Check if necessary to draw label
|
||||
if(drawDomain.includes(this.labelX)) {
|
||||
this.drawLabel(canvas, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
147
common/src/objs/bodephase.mjs
Normal file
147
common/src/objs/bodephase.mjs
Normal file
|
@ -0,0 +1,147 @@
|
|||
/**
|
||||
* 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 { executeExpression, Expression } from "../math/index.mjs"
|
||||
import { CreateNewObject } from "../history/index.mjs"
|
||||
import * as P from "../parameters.mjs"
|
||||
import Objects from "../module/objects.mjs"
|
||||
import History from "../module/history.mjs"
|
||||
import Latex from "../module/latex.mjs"
|
||||
|
||||
import { ExecutableObject } from "./common.mjs"
|
||||
|
||||
|
||||
export default class BodePhase extends ExecutableObject {
|
||||
static type() {
|
||||
return "Phase Bode"
|
||||
}
|
||||
|
||||
static displayType() {
|
||||
return qsTranslate("bodephase", "Bode Phase")
|
||||
}
|
||||
|
||||
static displayTypeMultiple() {
|
||||
return qsTranslate("bodephase", "Bode Phases")
|
||||
}
|
||||
|
||||
static properties() {
|
||||
return {
|
||||
[QT_TRANSLATE_NOOP("prop", "om_0")]: new P.ObjectType("Point"),
|
||||
[QT_TRANSLATE_NOOP("prop", "phase")]: new P.Expression(),
|
||||
[QT_TRANSLATE_NOOP("prop", "unit")]: new P.Enum("°", "deg", "rad"),
|
||||
[QT_TRANSLATE_NOOP("prop", "labelPosition")]: P.Enum.Position,
|
||||
[QT_TRANSLATE_NOOP("prop", "labelX")]: "number"
|
||||
}
|
||||
}
|
||||
|
||||
constructor(name = null, visible = true, color = null, labelContent = "name + value",
|
||||
om_0 = "", phase = 90, unit = "°", labelPosition = "above", labelX = 1) {
|
||||
if(name == null) name = Objects.getNewName("φ")
|
||||
if(name === "φ") name = "φ₀" // φ is reserved for sum of Bode phases.
|
||||
super(name, visible, color, labelContent)
|
||||
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
|
||||
om_0 = Objects.currentObjectsByName[om_0]
|
||||
if(om_0 == null) {
|
||||
// Create new point
|
||||
om_0 = Objects.createNewRegisteredObject("Point", [Objects.getNewName("ω"), this.color, "name"])
|
||||
om_0.labelPosition = this.phase.execute() >= 0 ? "above" : "below"
|
||||
History.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
|
||||
}
|
||||
|
||||
getReadableString() {
|
||||
return `${this.name}: ${this.phase.toString(true)}${this.unit} at ${this.om_0.name} = ${this.om_0.x}`
|
||||
}
|
||||
|
||||
getLatexString() {
|
||||
return `${Latex.variable(this.name)}: ${this.phase.latexMarkup}\\textsf{${this.unit} at }${Latex.variable(this.om_0.name)} = ${this.om_0.x.latexMarkup}`
|
||||
}
|
||||
|
||||
execute(x = 1) {
|
||||
if(typeof x == "string") x = executeExpression(x)
|
||||
if(x < this.om_0.x) {
|
||||
return this.om_0.y.execute()
|
||||
} else {
|
||||
return this.om_0.y.execute() + this.phase.execute()
|
||||
}
|
||||
}
|
||||
|
||||
simplify(x = 1) {
|
||||
let xval = x
|
||||
if(typeof x == "string") xval = executeExpression(x)
|
||||
if(xval < this.om_0.x) {
|
||||
return this.om_0.y.toString()
|
||||
} else {
|
||||
let newExp = this.om_0.y.toEditableString() + " + " + this.phase.toEditableString()
|
||||
return new Expression(newExp)
|
||||
}
|
||||
}
|
||||
|
||||
canExecute(x = 1) {
|
||||
return true
|
||||
}
|
||||
|
||||
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(0, baseY, Math.min(baseX, canvas.height), baseY)
|
||||
// Transition line.
|
||||
canvas.drawLine(baseX, baseY, baseX, augmtY)
|
||||
// After change line
|
||||
canvas.drawLine(Math.max(0, baseX), augmtY, canvas.width, augmtY)
|
||||
|
||||
// Label
|
||||
this.drawLabel(canvas, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))
|
||||
}
|
||||
|
||||
update() {
|
||||
super.update()
|
||||
/** @type {BodePhaseSum[]} */
|
||||
let sumObjs = Objects.currentObjects["Somme phases Bode"]
|
||||
if(sumObjs !== undefined && sumObjs.length > 0) {
|
||||
sumObjs[0].recalculateCache()
|
||||
} else {
|
||||
Objects.createNewRegisteredObject("Somme phases Bode")
|
||||
}
|
||||
}
|
||||
|
||||
delete() {
|
||||
super.update()
|
||||
/** @type {BodePhaseSum[]} */
|
||||
let sumObjs = Objects.currentObjects["Somme phases Bode"]
|
||||
if(sumObjs !== undefined && sumObjs.length > 0) {
|
||||
sumObjs[0].recalculateCache()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
139
common/src/objs/bodephasesum.mjs
Normal file
139
common/src/objs/bodephasesum.mjs
Normal file
|
@ -0,0 +1,139 @@
|
|||
/**
|
||||
* 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 { executeExpression, Expression } from "../math/index.mjs"
|
||||
import * as P from "../parameters.mjs"
|
||||
import Objects from "../module/objects.mjs"
|
||||
import Latex from "../module/latex.mjs"
|
||||
|
||||
import { ExecutableObject } from "./common.mjs"
|
||||
|
||||
export default class BodePhaseSum extends ExecutableObject {
|
||||
static type() {
|
||||
return "Somme phases Bode"
|
||||
}
|
||||
|
||||
static displayType() {
|
||||
return qsTranslate("bodephasesum", "Bode Phases Sum")
|
||||
}
|
||||
|
||||
static displayTypeMultiple() {
|
||||
return qsTranslate("bodephasesum", "Bode Phases Sum")
|
||||
}
|
||||
|
||||
static createable() {
|
||||
return false
|
||||
}
|
||||
|
||||
static properties() {
|
||||
return {
|
||||
[QT_TRANSLATE_NOOP("prop", "labelPosition")]: P.Enum.Position,
|
||||
[QT_TRANSLATE_NOOP("prop", "labelX")]: "number"
|
||||
}
|
||||
}
|
||||
|
||||
constructor(name = null, visible = true, color = null, labelContent = "name + value",
|
||||
labelPosition = "above", labelX = 1) {
|
||||
if(name == null) name = "φ"
|
||||
super(name, visible, color, labelContent)
|
||||
this.labelPosition = labelPosition
|
||||
this.labelX = labelX
|
||||
this.recalculateCache()
|
||||
}
|
||||
|
||||
getReadableString() {
|
||||
return `${this.name} = ${Objects.getObjectsName("Phase Bode").join(" + ")}`
|
||||
}
|
||||
|
||||
getLatexString() {
|
||||
return `${Latex.variable(this.name)} = ${Objects.getObjectsName("Phase Bode").map(name => Latex.variable(name)).join(" + ")}`
|
||||
}
|
||||
|
||||
execute(x = 1) {
|
||||
if(typeof x == "string") x = 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) {
|
||||
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 Expression(this.phasesExprList[i])).simplify()
|
||||
}
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
canExecute(x = 1) {
|
||||
return true
|
||||
}
|
||||
|
||||
recalculateCache() {
|
||||
// Minimum to draw (can be expended if needed, just not infinite or it'll cause issues.
|
||||
let drawMin = 1e-20
|
||||
let drawMax = 1e20
|
||||
this.om0xList = [drawMin, drawMax]
|
||||
this.phasesList = [0]
|
||||
this.phasesExprList = ["0"]
|
||||
let phasesDict = new Map()
|
||||
let phasesExprDict = new Map()
|
||||
phasesDict.set(drawMax, 0)
|
||||
|
||||
let phaseObjects = Objects.currentObjects["Phase Bode"]
|
||||
if(phaseObjects === undefined || phaseObjects.length < 1) {
|
||||
Objects.deleteObject(this.name)
|
||||
} else {
|
||||
console.log("Recalculating cache phase")
|
||||
for(/** @type {BodePhase} */ let obj of phaseObjects) {
|
||||
this.om0xList.push(obj.om_0.x.execute())
|
||||
if(!phasesDict.has(obj.om_0.x.execute())) {
|
||||
phasesDict.set(obj.om_0.x.execute(), obj.phase.execute())
|
||||
phasesExprDict.set(obj.om_0.x.execute(), obj.phase.toEditableString())
|
||||
} else {
|
||||
phasesDict.set(obj.om_0.x.execute(), obj.phase.execute() + phasesDict.get(obj.om_0.x.execute()))
|
||||
phasesExprDict.set(obj.om_0.x.execute(), obj.phase.toEditableString() + "+" + phasesExprDict.get(obj.om_0.x.execute()))
|
||||
}
|
||||
this.phasesList[0] += obj.om_0.y.execute()
|
||||
this.phasesExprList[0] += "+" + obj.om_0.y.toEditableString()
|
||||
}
|
||||
this.om0xList.sort((a, b) => a - b)
|
||||
for(let i = 1; i < this.om0xList.length; i++) {
|
||||
this.phasesList[i] = this.phasesList[i - 1] + phasesDict.get(this.om0xList[i])
|
||||
this.phasesExprList[i] = this.phasesExprList[i - 1] + "+" + phasesDict.get(this.om0xList[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))
|
||||
}
|
||||
}
|
||||
|
418
common/src/objs/common.mjs
Normal file
418
common/src/objs/common.mjs
Normal file
|
@ -0,0 +1,418 @@
|
|||
/**
|
||||
* 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 { getRandomColor } from "../utils.mjs"
|
||||
import Objects from "../module/objects.mjs"
|
||||
import Latex from "../module/latex.mjs"
|
||||
import { ensureTypeSafety, serializesByPropertyType } from "../parameters.mjs"
|
||||
|
||||
// This file contains the default data to be imported from all other objects
|
||||
|
||||
/**
|
||||
* Class to extend for every type of object that
|
||||
* can be drawn on the canvas.
|
||||
*/
|
||||
export class DrawableObject {
|
||||
/**
|
||||
* Base name of the object. Needs to be constant over time.
|
||||
* @return {string} Type of the object.
|
||||
*/
|
||||
static type() {
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
/**
|
||||
* Translated name of the object to be shown to the user.
|
||||
* @return {string}
|
||||
*/
|
||||
static displayType() {
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
/**
|
||||
* Translated name of the object in plural form to be shown to the user.
|
||||
* @return {string}
|
||||
*/
|
||||
static displayTypeMultiple() {
|
||||
return "Unknowns"
|
||||
}
|
||||
|
||||
/**
|
||||
* True if this object can be created by the user, false if it can only
|
||||
* be instantiated by other objects
|
||||
* @return {boolean}
|
||||
*/
|
||||
static createable() {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* List of properties used in the Object Editor.
|
||||
*
|
||||
* Properties are set with key as property name and
|
||||
* value as it's type name (e.g 'numbers', 'string'...),
|
||||
* an Enum for enumerations, an ObjectType for DrawableObjects
|
||||
* with a specific type, a List instance for lists, a
|
||||
* Dictionary instance for dictionaries, an Expression for expressions...
|
||||
*
|
||||
* 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.mjs and
|
||||
* then be linked directly here.
|
||||
*
|
||||
* @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 {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 {boolean} visible - true if the object is visible, false otherwise.
|
||||
* @param {color|string} color - Color of the object (can be string or QColor)
|
||||
* @param {"null"|"name"|"name + value"} labelContent - One of 'null', 'name' or 'name + value' describing the content of the label.
|
||||
* @constructor
|
||||
*/
|
||||
constructor(name, visible = true, color = null, labelContent = "name + value") {
|
||||
if(color == null) color = getRandomColor()
|
||||
this.type = this.constructor.type()
|
||||
this.name = name
|
||||
this.visible = visible
|
||||
this.color = color
|
||||
this.labelContent = labelContent // "null", "name", "name + value"
|
||||
this.requiredBy = []
|
||||
this.requires = []
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* String representing the object that will be displayed to the user.
|
||||
* It allows for 2 lines and translated text, but it shouldn't be too long.
|
||||
* @return {string}
|
||||
*/
|
||||
getReadableString() {
|
||||
return `${this.name} = Unknown`
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.mjs for helper methods.
|
||||
* @return {string}
|
||||
*/
|
||||
getLatexString() {
|
||||
return this.getReadableString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Readable string content of the label depending on the value of the latexContent.
|
||||
* @return {string}
|
||||
*/
|
||||
getLabel() {
|
||||
switch(this.labelContent) {
|
||||
case "name":
|
||||
return this.name
|
||||
case "name + value":
|
||||
return this.getReadableString()
|
||||
case "null":
|
||||
return ""
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.mjs for helper methods.
|
||||
* @return {string}
|
||||
*/
|
||||
getLatexLabel() {
|
||||
switch(this.labelContent) {
|
||||
case "name":
|
||||
return Latex.variable(this.name)
|
||||
case "name + value":
|
||||
return this.getLatexString()
|
||||
case "null":
|
||||
return ""
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the recursive list of objects this one depends on.
|
||||
* @return {array}
|
||||
*/
|
||||
getDependenciesList() {
|
||||
let dependencies = this.requires.map(obj => obj)
|
||||
for(let obj of this.requires)
|
||||
dependencies = dependencies.concat(obj.getDependenciesList())
|
||||
return dependencies
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback method when one of the properties of the object is updated.
|
||||
*/
|
||||
update() {
|
||||
// Refreshing dependencies.
|
||||
for(let obj of this.requires)
|
||||
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) {
|
||||
// Expressions with dependencies
|
||||
for(let objName of this[property].requiredObjects()) {
|
||||
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) {
|
||||
// Object dependency
|
||||
this.requires.push(this[property])
|
||||
this[property].requiredBy.push(this)
|
||||
}
|
||||
// Updating objects dependent on this one
|
||||
for(let req of this.requiredBy)
|
||||
req.update()
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback method when the object is about to get deleted.
|
||||
*/
|
||||
delete() {
|
||||
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 canvas with the.
|
||||
* @param {CanvasAPI} canvas
|
||||
*/
|
||||
draw(canvas) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {{width: number, height: number}} 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
|
||||
*/
|
||||
drawPositionDivergence(labelPosition, offset, size, posX, posY, drawFunction) {
|
||||
switch(labelPosition) {
|
||||
case "center":
|
||||
drawFunction(posX - size.width / 2, posY - size.height / 2)
|
||||
break
|
||||
case "top":
|
||||
case "above":
|
||||
drawFunction(posX - size.width / 2, posY - size.height - offset)
|
||||
break
|
||||
case "bottom":
|
||||
case "below":
|
||||
drawFunction(posX - size.width / 2, posY + offset)
|
||||
break
|
||||
case "left":
|
||||
drawFunction(posX - size.width - offset, posY - size.height / 2)
|
||||
break
|
||||
case "right":
|
||||
drawFunction(posX + offset, posY - size.height / 2)
|
||||
break
|
||||
case "top-left":
|
||||
case "above-left":
|
||||
drawFunction(posX - size.width, posY - size.height - offset)
|
||||
break
|
||||
case "top-right":
|
||||
case "above-right":
|
||||
drawFunction(posX + offset, posY - size.height - offset)
|
||||
break
|
||||
case "bottom-left":
|
||||
case "below-left":
|
||||
drawFunction(posX - size.width - offset, posY + offset)
|
||||
break
|
||||
case "bottom-right":
|
||||
case "below-right":
|
||||
drawFunction(posX + offset, posY + offset)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 drawFunctionLatex (x,y,imageData) and
|
||||
* drawFunctionText (x,y,text) depending on whether to use latex.
|
||||
*
|
||||
* @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 {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, labelPosition, posX, posY, forceText = false,
|
||||
getLatexFunction = null, getTextFunction = null, drawFunctionLatex = null, drawFunctionText = null) {
|
||||
// Default functions
|
||||
if(getLatexFunction == null)
|
||||
getLatexFunction = this.getLatexLabel.bind(this)
|
||||
if(getTextFunction == null)
|
||||
getTextFunction = this.getLabel.bind(this)
|
||||
if(drawFunctionLatex == null)
|
||||
drawFunctionLatex = (x, y, ltxImg) => canvas.drawVisibleImage(ltxImg.source, x, y, ltxImg.width, ltxImg.height)
|
||||
if(drawFunctionText == null)
|
||||
drawFunctionText = (x, y, text, textSize) => canvas.drawVisibleText(text, x, y + textSize.height) // Positioned from left bottom
|
||||
// Drawing the label
|
||||
if(!forceText && Latex.enabled) {
|
||||
// With latex
|
||||
let drawLblCb = ((ltxImg) => {
|
||||
this.drawPositionDivergence(labelPosition, 8 + canvas.linewidth / 2, ltxImg, posX, posY, (x, y) => drawFunctionLatex(x, y, ltxImg))
|
||||
})
|
||||
let ltxLabel = getLatexFunction()
|
||||
if(ltxLabel !== "")
|
||||
canvas.renderLatexImage(ltxLabel, this.color, drawLblCb)
|
||||
} else {
|
||||
// Without latex
|
||||
let text = getTextFunction()
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.name
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Class to be extended for every object on which
|
||||
* an y for a x can be computed with the execute function.
|
||||
* If a value cannot be found during execute, it will
|
||||
* return null. However, theses same x values will
|
||||
* return false when passed to canExecute.
|
||||
*/
|
||||
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
|
||||
* this function will return null.
|
||||
*
|
||||
* @param {number} x
|
||||
* @returns {number|null}
|
||||
*/
|
||||
execute(x = 1) {
|
||||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false if the object isn't defined on the given x, true otherwise.
|
||||
*
|
||||
* @param {number} x
|
||||
* @returns {boolean}
|
||||
*/
|
||||
canExecute(x = 1) {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the simplified expression string for a given x.
|
||||
*
|
||||
* @param {number} x
|
||||
* @returns {string|Expression}
|
||||
*/
|
||||
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 {boolean}
|
||||
*/
|
||||
static executable() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
159
common/src/objs/distribution.mjs
Normal file
159
common/src/objs/distribution.mjs
Normal file
|
@ -0,0 +1,159 @@
|
|||
/**
|
||||
* 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 * as P from "../parameters.mjs"
|
||||
import Objects from "../module/objects.mjs"
|
||||
import Latex from "../module/latex.mjs"
|
||||
|
||||
import { ExecutableObject } from "./common.mjs"
|
||||
|
||||
|
||||
export default class DistributionFunction extends ExecutableObject {
|
||||
static type() {
|
||||
return "Repartition"
|
||||
}
|
||||
|
||||
static displayType() {
|
||||
return qsTranslate("distribution", "Repartition")
|
||||
}
|
||||
|
||||
static displayTypeMultiple() {
|
||||
return qsTranslate("distribution", "Repartition functions")
|
||||
}
|
||||
|
||||
static properties() {
|
||||
return {
|
||||
"comment1": QT_TRANSLATE_NOOP(
|
||||
"comment",
|
||||
"Note: Specify the probability for each value."
|
||||
),
|
||||
[QT_TRANSLATE_NOOP("prop", "probabilities")]: new P.Dictionary("string", "double", /^-?[\d.,]+$/, /^-?[\d.,]+$/, "P({name_} = ", ") = "),
|
||||
[QT_TRANSLATE_NOOP("prop", "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",
|
||||
probabilities = { "0": "0" }, labelPosition = "above", labelX = 1) {
|
||||
if(name == null) name = Objects.getNewName("XYZUVW", "F_")
|
||||
super(name, visible, color, labelContent)
|
||||
this.probabilities = probabilities
|
||||
this.labelPosition = labelPosition
|
||||
this.labelX = labelX
|
||||
this.update()
|
||||
}
|
||||
|
||||
getReadableString() {
|
||||
let keys = Object.keys(this.probabilities).sort((a, b) => a - b)
|
||||
let varname = this.name.substring(this.name.indexOf("_") + 1)
|
||||
return `${this.name}(x) = P(${varname} ≤ x)\n` + keys.map(idx => `P(${varname}=${idx})=${this.probabilities[idx]}`).join("; ")
|
||||
}
|
||||
|
||||
getLatexString() {
|
||||
let keys = Object.keys(this.probabilities).sort((a, b) => a - b)
|
||||
let funcName = Latex.variable(this.name)
|
||||
let varName = Latex.variable(this.name.substring(this.name.indexOf("_") + 1))
|
||||
return `\\begin{array}{l}{${funcName}}(x) = P(${varName} \\le x)\\\\` + keys.map(idx => `P(${varName}=${idx})=${this.probabilities[idx]}`).join("; ") + "\\end{array}"
|
||||
}
|
||||
|
||||
execute(x = 1) {
|
||||
let ret = 0
|
||||
Object.keys(this.probabilities).sort((a, b) => a - b).forEach(idx => {
|
||||
if(x >= idx) ret += parseFloat(this.probabilities[idx].replace(/,/g, "."))
|
||||
})
|
||||
return ret
|
||||
}
|
||||
|
||||
canExecute(x = 1) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Simplify returns the simplified string of the expression.
|
||||
simplify(x = 1) {
|
||||
return this.execute(x).toString()
|
||||
}
|
||||
|
||||
getLabel() {
|
||||
switch(this.labelContent) {
|
||||
case "name":
|
||||
return `${this.name}(x)`
|
||||
case "name + value":
|
||||
return this.getReadableString()
|
||||
case "null":
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
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(0, canvas.y2px(0), canvas.x2px(keys[0]), canvas.y2px(0))
|
||||
if(canvas.isVisible(keys[0], 0)) {
|
||||
canvas.arc(canvas.x2px(keys[0]) + 4, canvas.y2px(0), 4, Math.PI / 2, 3 * Math.PI / 2)
|
||||
}
|
||||
}
|
||||
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(
|
||||
Math.max(0, canvas.x2px(idx)),
|
||||
canvas.y2px(currentY),
|
||||
Math.min(canvas.width, canvas.x2px(keys[i + 1])),
|
||||
canvas.y2px(currentY)
|
||||
)
|
||||
if(canvas.isVisible(idx, currentY)) {
|
||||
canvas.disc(canvas.x2px(idx), canvas.y2px(currentY), 4)
|
||||
}
|
||||
if(canvas.isVisible(keys[i + 1], currentY)) {
|
||||
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(
|
||||
Math.max(0, canvas.x2px(keys[keys.length - 1])),
|
||||
canvas.y2px(currentY + parseFloat(this.probabilities[keys[keys.length - 1]].replace(/,/g, "."))),
|
||||
canvas.width,
|
||||
canvas.y2px(currentY + parseFloat(this.probabilities[keys[keys.length - 1]].replace(/,/g, ".")))
|
||||
)
|
||||
canvas.disc(
|
||||
canvas.x2px(keys[keys.length - 1]),
|
||||
canvas.y2px(
|
||||
currentY + parseFloat(this.probabilities[keys[keys.length - 1]].replace(/,/g, "."))
|
||||
),
|
||||
4
|
||||
)
|
||||
}
|
||||
|
||||
// Label
|
||||
this.drawLabel(canvas, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))
|
||||
}
|
||||
}
|
||||
|
201
common/src/objs/function.mjs
Normal file
201
common/src/objs/function.mjs
Normal file
|
@ -0,0 +1,201 @@
|
|||
/**
|
||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||
* Copyright (C) 2021-2024 Ad5001
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { textsub } from "../utils.mjs"
|
||||
import Objects from "../module/objects.mjs"
|
||||
import { ExecutableObject } from "./common.mjs"
|
||||
import { parseDomain, Expression, SpecialDomain } from "../math/index.mjs"
|
||||
import * as P from "../parameters.mjs"
|
||||
import Latex from "../module/latex.mjs"
|
||||
|
||||
|
||||
export default class Function extends ExecutableObject {
|
||||
static type() {
|
||||
return "Function"
|
||||
}
|
||||
|
||||
static displayType() {
|
||||
return qsTranslate("function", "Function")
|
||||
}
|
||||
|
||||
static displayTypeMultiple() {
|
||||
return qsTranslate("function", "Functions")
|
||||
}
|
||||
|
||||
static properties() {
|
||||
return {
|
||||
[QT_TRANSLATE_NOOP("prop", "expression")]: new P.Expression("x"),
|
||||
[QT_TRANSLATE_NOOP("prop", "definitionDomain")]: "Domain",
|
||||
[QT_TRANSLATE_NOOP("prop", "destinationDomain")]: "Domain",
|
||||
"comment1": QT_TRANSLATE_NOOP(
|
||||
"comment",
|
||||
"Ex: R+* (ℝ⁺*), N (ℕ), Z-* (ℤ⁻*), ]0;1[, {3;4;5}"
|
||||
),
|
||||
[QT_TRANSLATE_NOOP("prop", "displayMode")]: P.Enum.FunctionDisplayType,
|
||||
[QT_TRANSLATE_NOOP("prop", "labelPosition")]: P.Enum.Position,
|
||||
[QT_TRANSLATE_NOOP("prop", "labelX")]: "number",
|
||||
"comment2": QT_TRANSLATE_NOOP(
|
||||
"comment",
|
||||
"The following parameters are used when the definition domain is a non-continuous set. (Ex: ℕ, ℤ, sets like {0;3}...)"
|
||||
),
|
||||
[QT_TRANSLATE_NOOP("prop", "drawPoints")]: "boolean",
|
||||
[QT_TRANSLATE_NOOP("prop", "drawDashedLines")]: "boolean"
|
||||
}
|
||||
}
|
||||
|
||||
constructor(name = null, visible = true, color = null, labelContent = "name + value",
|
||||
expression = "x", definitionDomain = "RPE", destinationDomain = "R",
|
||||
displayMode = "application", labelPosition = "above", labelX = 1,
|
||||
drawPoints = true, drawDashedLines = true) {
|
||||
if(name == null) name = Objects.getNewName("fghjqlmnopqrstuvwabcde")
|
||||
super(name, visible, color, labelContent)
|
||||
if(typeof expression == "number" || typeof expression == "string") expression = new Expression(expression.toString())
|
||||
this.expression = expression
|
||||
if(typeof definitionDomain == "string") definitionDomain = parseDomain(definitionDomain)
|
||||
this.definitionDomain = definitionDomain
|
||||
if(typeof destinationDomain == "string") destinationDomain = parseDomain(destinationDomain)
|
||||
this.destinationDomain = destinationDomain
|
||||
this.displayMode = displayMode
|
||||
this.labelPosition = labelPosition
|
||||
this.labelX = labelX
|
||||
this.drawPoints = drawPoints
|
||||
this.drawDashedLines = drawDashedLines
|
||||
}
|
||||
|
||||
getReadableString() {
|
||||
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${textsub(this.name)} = ${this.definitionDomain}`
|
||||
}
|
||||
}
|
||||
|
||||
getLatexString() {
|
||||
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}`
|
||||
}
|
||||
}
|
||||
|
||||
execute(x = 1) {
|
||||
if(this.definitionDomain.includes(x))
|
||||
return this.expression.execute(x)
|
||||
return null
|
||||
}
|
||||
|
||||
canExecute(x = 1) {
|
||||
return this.definitionDomain.includes(x)
|
||||
}
|
||||
|
||||
simplify(x = 1) {
|
||||
if(this.definitionDomain.includes(x))
|
||||
return this.expression.simplify(x)
|
||||
return ""
|
||||
}
|
||||
|
||||
draw(canvas) {
|
||||
Function.drawFunction(canvas, this.expression, this.definitionDomain, this.destinationDomain, this.drawPoints, this.drawDashedLines)
|
||||
// Label
|
||||
this.drawLabel(canvas, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.width) {
|
||||
// Reconverted for canvas to fix for logarithmic scales.
|
||||
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(canvas.x2px(previousX), canvas.y2px(previousY), canvas.x2px(currentX), canvas.y2px(currentY))
|
||||
if(drawPoints) {
|
||||
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
|
||||
previousY = currentY
|
||||
}
|
||||
if(drawPoints) {
|
||||
// Drawing the last cross
|
||||
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 {
|
||||
// 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))
|
||||
}
|
||||
}
|
||||
previousY = currentY
|
||||
} else {
|
||||
previousY = null // Last y was invalid, so let's not draw anything from it.
|
||||
}
|
||||
previousX = canvas.px2x(px)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
87
common/src/objs/point.mjs
Normal file
87
common/src/objs/point.mjs
Normal file
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
* 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 { Expression } from "../math/index.mjs"
|
||||
import * as P from "../parameters.mjs"
|
||||
import Objects from "../module/objects.mjs"
|
||||
import Latex from "../module/latex.mjs"
|
||||
|
||||
import { DrawableObject } from "./common.mjs"
|
||||
|
||||
|
||||
export default class Point extends DrawableObject {
|
||||
static type() {
|
||||
return "Point"
|
||||
}
|
||||
|
||||
static displayType() {
|
||||
return qsTranslate("point", "Point")
|
||||
}
|
||||
|
||||
static displayTypeMultiple() {
|
||||
return qsTranslate("point", "Points")
|
||||
}
|
||||
|
||||
static properties() {
|
||||
return {
|
||||
[QT_TRANSLATE_NOOP("prop", "x")]: new P.Expression(),
|
||||
[QT_TRANSLATE_NOOP("prop", "y")]: new P.Expression(),
|
||||
[QT_TRANSLATE_NOOP("prop", "labelPosition")]: P.Enum.Position,
|
||||
[QT_TRANSLATE_NOOP("prop", "pointStyle")]: new P.Enum("●", "✕", "+")
|
||||
}
|
||||
}
|
||||
|
||||
constructor(name = null, visible = true, color = null, labelContent = "name + value",
|
||||
x = 1, y = 0, labelPosition = "above", pointStyle = "●") {
|
||||
if(name == null) name = Objects.getNewName("ABCDEFJKLMNOPQRSTUVW")
|
||||
super(name, visible, color, labelContent)
|
||||
if(typeof x == "number" || typeof x == "string") x = new Expression(x.toString())
|
||||
this.x = x
|
||||
if(typeof y == "number" || typeof y == "string") y = new Expression(y.toString())
|
||||
this.y = y
|
||||
this.labelPosition = labelPosition
|
||||
this.pointStyle = pointStyle
|
||||
}
|
||||
|
||||
getReadableString() {
|
||||
return `${this.name} = (${this.x}, ${this.y})`
|
||||
}
|
||||
|
||||
getLatexString() {
|
||||
return `${Latex.variable(this.name)} = \\left(${this.x.latexMarkup}, ${this.y.latexMarkup}\\right)`
|
||||
}
|
||||
|
||||
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 "●":
|
||||
canvas.disc(canvasX, canvasY, pointSize / 2)
|
||||
break
|
||||
case "✕":
|
||||
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 "+":
|
||||
canvas.fillRect(canvasX - pointSize / 2, canvasY - 1, pointSize, 2)
|
||||
canvas.fillRect(canvasX - 1, canvasY - pointSize / 2, 2, pointSize)
|
||||
break
|
||||
}
|
||||
this.drawLabel(canvas, this.labelPosition, canvasX, canvasY)
|
||||
}
|
||||
}
|
140
common/src/objs/sequence.mjs
Normal file
140
common/src/objs/sequence.mjs
Normal file
|
@ -0,0 +1,140 @@
|
|||
/**
|
||||
* 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 { Sequence as MathSequence, Domain } from "../math/index.mjs"
|
||||
import * as P from "../parameters.mjs"
|
||||
import Latex from "../module/latex.mjs"
|
||||
import Objects from "../module/objects.mjs"
|
||||
|
||||
import { ExecutableObject } from "./common.mjs"
|
||||
import Function from "./function.mjs"
|
||||
|
||||
|
||||
export default class Sequence extends ExecutableObject {
|
||||
static type() {
|
||||
return "Sequence"
|
||||
}
|
||||
|
||||
static displayType() {
|
||||
return qsTranslate("sequence", "Sequence")
|
||||
}
|
||||
|
||||
static displayTypeMultiple() {
|
||||
return qsTranslate("sequence", "Sequences")
|
||||
}
|
||||
|
||||
static properties() {
|
||||
return {
|
||||
[QT_TRANSLATE_NOOP("prop", "drawPoints")]: "boolean",
|
||||
[QT_TRANSLATE_NOOP("prop", "drawDashedLines")]: "boolean",
|
||||
[QT_TRANSLATE_NOOP("prop", "defaultExpression")]: new P.Dictionary("string", "int", /^.+$/, /^\d+$/, "{name}[n+", "] = ", true),
|
||||
"comment1": QT_TRANSLATE_NOOP(
|
||||
"comment",
|
||||
"Note: Use %1[n] to refer to %1ₙ, %1[n+1] for %1ₙ₊₁..."
|
||||
),
|
||||
[QT_TRANSLATE_NOOP("prop", "baseValues")]: new P.Dictionary("string", "int", /^.+$/, /^\d+$/, "{name}[", "] = "),
|
||||
[QT_TRANSLATE_NOOP("prop", "labelPosition")]: P.Enum.Position,
|
||||
[QT_TRANSLATE_NOOP("prop", "labelX")]: "number"
|
||||
}
|
||||
}
|
||||
|
||||
constructor(name = null, visible = true, color = null, labelContent = "name + value",
|
||||
drawPoints = true, drawDashedLines = true, defaultExp = { 1: "n" },
|
||||
baseValues = { 0: 0 }, labelPosition = "above", labelX = 1) {
|
||||
if(name == null) name = Objects.getNewName("uvwPSUVWabcde")
|
||||
super(name, visible, color, labelContent)
|
||||
this.drawPoints = drawPoints
|
||||
this.drawDashedLines = drawDashedLines
|
||||
this.defaultExpression = defaultExp
|
||||
this.baseValues = baseValues
|
||||
this.labelPosition = labelPosition
|
||||
this.labelX = labelX
|
||||
this.update()
|
||||
}
|
||||
|
||||
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.toString() !== Object.keys(this.defaultExpression)[0]
|
||||
)
|
||||
this.sequence = new MathSequence(
|
||||
this.name, this.baseValues,
|
||||
parseFloat(Object.keys(this.defaultExpression)[0]),
|
||||
Object.values(this.defaultExpression)[0]
|
||||
)
|
||||
}
|
||||
|
||||
getReadableString() {
|
||||
return this.sequence.toString()
|
||||
}
|
||||
|
||||
getLatexString() {
|
||||
return this.sequence.toLatexString()
|
||||
}
|
||||
|
||||
execute(x = 1) {
|
||||
if(x % 1 === 0)
|
||||
return this.sequence.execute(x)
|
||||
return null
|
||||
}
|
||||
|
||||
canExecute(x = 1) {
|
||||
return x % 1 === 0
|
||||
}
|
||||
|
||||
// Simplify returns the simplified string of the expression.
|
||||
simplify(x = 1) {
|
||||
if(x % 1 === 0)
|
||||
return this.sequence.simplify(x)
|
||||
return null
|
||||
}
|
||||
|
||||
getLabel() {
|
||||
switch(this.labelContent) {
|
||||
case "name":
|
||||
return `(${this.name}ₙ)`
|
||||
case "name + value":
|
||||
return this.getReadableString()
|
||||
case "null":
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
getLatexLabel() {
|
||||
switch(this.labelContent) {
|
||||
case "name":
|
||||
return `(${Latex.variable(this.name)}_n)`
|
||||
case "name + value":
|
||||
return this.getLatexString()
|
||||
case "null":
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
draw(canvas) {
|
||||
Function.drawFunction(canvas, this.sequence, canvas.logscalex ? Domain.NE : Domain.N, Domain.R, this.drawPoints, this.drawDashedLines)
|
||||
|
||||
// Label
|
||||
this.drawLabel(canvas, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))
|
||||
}
|
||||
}
|
||||
|
108
common/src/objs/text.mjs
Normal file
108
common/src/objs/text.mjs
Normal file
|
@ -0,0 +1,108 @@
|
|||
/**
|
||||
* 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 { Expression } from "../math/index.mjs"
|
||||
import * as P from "../parameters.mjs"
|
||||
import Objects from "../module/objects.mjs"
|
||||
import Latex from "../module/latex.mjs"
|
||||
|
||||
import { DrawableObject } from "./common.mjs"
|
||||
|
||||
|
||||
export default class Text extends DrawableObject {
|
||||
static type() {
|
||||
return "Text"
|
||||
}
|
||||
|
||||
static displayType() {
|
||||
return qsTranslate("text", "Text")
|
||||
}
|
||||
|
||||
static displayTypeMultiple() {
|
||||
return qsTranslate("text", "Texts")
|
||||
}
|
||||
|
||||
static properties() {
|
||||
return {
|
||||
[QT_TRANSLATE_NOOP("prop", "x")]: new P.Expression(),
|
||||
[QT_TRANSLATE_NOOP("prop", "y")]: new P.Expression(),
|
||||
[QT_TRANSLATE_NOOP("prop", "labelPosition")]: P.Enum.Positioning,
|
||||
[QT_TRANSLATE_NOOP("prop", "text")]: "string",
|
||||
"comment1": QT_TRANSLATE_NOOP(
|
||||
"comment",
|
||||
"If you have latex enabled, you can use use latex markup in between $$ to create equations."
|
||||
),
|
||||
[QT_TRANSLATE_NOOP("prop", "disableLatex")]: "boolean"
|
||||
}
|
||||
}
|
||||
|
||||
constructor(name = null, visible = true, color = null, labelContent = "null",
|
||||
x = 1, y = 0, labelPosition = "center", text = "New text", disableLatex = false) {
|
||||
if(name == null) name = Objects.getNewName("t")
|
||||
super(name, visible, color, labelContent)
|
||||
if(typeof x == "number" || typeof x == "string") x = new Expression(x.toString())
|
||||
this.x = x
|
||||
if(typeof y == "number" || typeof y == "string") y = new Expression(y.toString())
|
||||
this.y = y
|
||||
this.labelPosition = labelPosition
|
||||
this.text = text
|
||||
this.disableLatex = disableLatex
|
||||
}
|
||||
|
||||
getReadableString() {
|
||||
return `${this.name} = "${this.text}"`
|
||||
}
|
||||
|
||||
latexMarkupText() {
|
||||
// Check whether the text contains latex escaped elements.
|
||||
let txt = []
|
||||
this.text.split("$$").forEach(function(t) {
|
||||
txt = txt.concat(Latex.variable(t, true).replace(/\$\$/g, "").split("$"))
|
||||
})
|
||||
let newTxt = txt[0]
|
||||
let i
|
||||
// Split between normal text and latex escaped.
|
||||
for(i = 0; i < txt.length - 1; i++)
|
||||
if(i & 0x01) // Every odd number
|
||||
newTxt += "\\textsf{" + Latex.variable(txt[i + 1])
|
||||
else
|
||||
newTxt += "}" + txt[i + 1]
|
||||
// Finished by a }
|
||||
if(i & 0x01)
|
||||
newTxt += "{"
|
||||
return newTxt
|
||||
}
|
||||
|
||||
getLatexString() {
|
||||
return `${Latex.variable(this.name)} = "\\textsf{${this.latexMarkupText()}}"`
|
||||
}
|
||||
|
||||
getLabel() {
|
||||
return this.text
|
||||
}
|
||||
|
||||
getLatexLabel() {
|
||||
return `\\textsf{${this.latexMarkupText()}}`
|
||||
}
|
||||
|
||||
draw(canvas) {
|
||||
let yOffset = this.disableLatex ? canvas.textsize - 4 : 0
|
||||
this.drawLabel(canvas, this.labelPosition, canvas.x2px(this.x.execute()), canvas.y2px(this.y.execute()) + yOffset, this.disableLatex)
|
||||
}
|
||||
}
|
||||
|
167
common/src/objs/xcursor.mjs
Normal file
167
common/src/objs/xcursor.mjs
Normal file
|
@ -0,0 +1,167 @@
|
|||
/**
|
||||
* 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 { Expression } from "../math/index.mjs"
|
||||
import * as P from "../parameters.mjs"
|
||||
import Latex from "../module/latex.mjs"
|
||||
import Objects from "../module/objects.mjs"
|
||||
|
||||
import { DrawableObject } from "./common.mjs"
|
||||
|
||||
|
||||
export default class XCursor extends DrawableObject {
|
||||
static type(){return 'X Cursor'}
|
||||
static displayType(){return qsTranslate("xcursor", 'X Cursor')}
|
||||
static displayTypeMultiple(){return qsTranslate("xcursor", 'X Cursors')}
|
||||
static properties() {return {
|
||||
[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')]: 'int',
|
||||
[QT_TRANSLATE_NOOP('prop','displayStyle')]: new P.Enum(
|
||||
'— — — — — — —',
|
||||
'⸺⸺⸺⸺⸺⸺',
|
||||
'• • • • • • • • • •'
|
||||
),
|
||||
[QT_TRANSLATE_NOOP('prop','targetValuePosition')]: P.Enum.XCursorValuePosition,
|
||||
}}
|
||||
|
||||
constructor(name = null, visible = true, color = null, labelContent = 'name + value',
|
||||
x = 1, targetElement = null, labelPosition = 'left', approximate = true,
|
||||
rounding = 3, displayStyle = '— — — — — — —', targetValuePosition = 'Next to target') {
|
||||
if(name == null) name = Objects.getNewName('X')
|
||||
super(name, visible, color, labelContent)
|
||||
this.approximate = approximate
|
||||
this.rounding = rounding
|
||||
if(typeof x == 'number' || typeof x == 'string') x = new Expression(x.toString())
|
||||
this.x = x
|
||||
this.targetElement = targetElement
|
||||
if(typeof targetElement == "string") {
|
||||
this.targetElement = Objects.currentObjectsByName[targetElement]
|
||||
}
|
||||
this.labelPosition = labelPosition
|
||||
this.displayStyle = displayStyle
|
||||
this.targetValuePosition = targetValuePosition
|
||||
}
|
||||
|
||||
getReadableString() {
|
||||
if(this.targetElement == null) return `${this.name} = ${this.x.toString()}`
|
||||
return `${this.name} = ${this.x.toString()}\n${this.getTargetValueLabel()}`
|
||||
}
|
||||
|
||||
getLatexString() {
|
||||
if(this.targetElement == null) return `${Latex.variable(this.name)} = ${this.x.latexMarkup}`
|
||||
return `\\begin{array}{l}
|
||||
${Latex.variable(this.name)} = ${this.x.latexMarkup} \\\\
|
||||
${this.getTargetValueLatexLabel()}
|
||||
\\end{array}`
|
||||
}
|
||||
|
||||
getApprox() {
|
||||
let approx = ''
|
||||
if(this.approximate) {
|
||||
approx = (this.targetElement.execute(this.x.execute()))
|
||||
let intLength = Math.round(approx).toString().length
|
||||
let rounding = Math.min(this.rounding, approx.toString().length - intLength - 1)
|
||||
approx = approx.toPrecision(rounding + intLength)
|
||||
}
|
||||
return approx
|
||||
}
|
||||
|
||||
getTargetValueLabel() {
|
||||
const t = this.targetElement
|
||||
const approx = this.getApprox()
|
||||
return `${t.name}(${this.name}) = ${t.simplify(this.x.toEditableString())}` +
|
||||
(this.approximate ? ' ≈ ' + approx : '')
|
||||
}
|
||||
|
||||
getTargetValueLatexLabel() {
|
||||
const t = this.targetElement
|
||||
const approx = this.getApprox()
|
||||
const simpl = t.simplify(this.x.toEditableString())
|
||||
return `${Latex.variable(t.name)}(${Latex.variable(this.name)}) = ${simpl.latexMarkup ? simpl.latexMarkup : Latex.variable(simpl)}` +
|
||||
(this.approximate ? ' \\simeq ' + approx : '')
|
||||
}
|
||||
|
||||
getLabel() {
|
||||
switch(this.labelContent) {
|
||||
case 'name':
|
||||
return this.name
|
||||
case 'name + value':
|
||||
switch(this.targetValuePosition) {
|
||||
case 'Next to target':
|
||||
case 'Hidden':
|
||||
return `${this.name} = ${this.x.toString()}`
|
||||
case 'With label':
|
||||
return this.getReadableString()
|
||||
}
|
||||
case 'null':
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
getLatexLabel() {
|
||||
switch(this.labelContent) {
|
||||
case 'name':
|
||||
return Latex.variable(this.name)
|
||||
case 'name + value':
|
||||
switch(this.targetValuePosition) {
|
||||
case 'Next to target':
|
||||
case 'Hidden':
|
||||
return `${Latex.variable(this.name)} = ${this.x.latexMarkup}`
|
||||
case 'With label':
|
||||
return this.getLatexString()
|
||||
}
|
||||
case 'null':
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
draw(canvas) {
|
||||
let xpos = canvas.x2px(this.x.execute())
|
||||
switch(this.displayStyle) {
|
||||
case '— — — — — — —':
|
||||
canvas.drawDashedLine(xpos, 0, xpos, canvas.height, 20)
|
||||
break;
|
||||
case '⸺⸺⸺⸺⸺⸺':
|
||||
canvas.drawXLine(this.x.execute())
|
||||
break;
|
||||
case '• • • • • • • • • •':
|
||||
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, 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) {
|
||||
let ypos = canvas.y2px(this.targetElement.execute(this.x.execute()))
|
||||
this.drawLabel(canvas, this.labelPosition, xpos, ypos, false,
|
||||
this.getTargetValueLatexLabel.bind(this), this.getTargetValueLabel.bind(this),
|
||||
(x,y,ltxImg) => canvas.drawVisibleImage(ltxImg.source, x, y, ltxImg.width, ltxImg.height),
|
||||
(x,y,text,textSize) => canvas.drawVisibleText(text, x, y+textSize.height))
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue