LogarithmPlotter/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/function.js
Ad5001 0975189615
Some checks reported errors
continuous-integration/drone/push Build was killed
A lot of changes related to latex:
- Implemented latex for functions
- Fixed Points with greek variable names
- Small changes to test1 to fit latex better
- History re/undos only redraw the graph every 4 change in order to speed up the process when re/undoing a lot of changes.
- Removing some debug related to latex
- Added latexMarkup property for domains, allowing them to be integrated into objects latex.
- Fixed issues related to derivatives and integrals on latex
- Fully fixed variable substitution for latex
- Fixed sequence crashing
- Adding getLatexLabel method for objects that have a latex label.
2022-03-06 00:55:32 +01:00

241 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.pragma library
.import "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
class Function extends Common.ExecutableObject {
static type(){return 'Function'}
static displayType(){return qsTr('Function')}
static displayTypeMultiple(){return qsTr('Functions')}
/*static properties() {return {
'expression': 'Expression',
'definitionDomain': 'Domain',
'destinationDomain': 'Domain',
'comment1': 'Ex: R+* (ℝ⁺*), N (), Z-* (ℤ⁻*), ]0;1[, {3;4;5}',
'labelPosition': P.Enum.Position,
'displayMode': new P.Enum('application', 'function'),
'labelX': 'number',
'comment2': 'The following parameters are used when the definition domain is a non-continuous set. (Ex: , , sets like {0;3}...)',
'drawPoints': 'boolean',
'drawDashedLines': 'boolean'
}}*/
static properties() {return {
[QT_TRANSLATE_NOOP('prop','expression')]: 'Expression',
[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','labelPosition')]: P.Enum.Position,
[QT_TRANSLATE_NOOP('prop','displayMode')]: P.Enum.FunctionDisplayType,
[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 = Common.getNewName('fghjqlmnopqrstuvwabcde')
super(name, visible, color, labelContent)
this.type = 'Function'
if(typeof expression == 'number' || typeof expression == 'string') expression = new MathLib.Expression(expression.toString())
this.expression = expression
if(typeof definitionDomain == 'string') definitionDomain = MathLib.parseDomain(definitionDomain)
this.definitionDomain = definitionDomain
if(typeof destinationDomain == 'string') destinationDomain = MathLib.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${Utils.textsub(this.name)} = ${this.definitionDomain}`
}
}
toLatexString() {
if(this.displayMode == 'application') {
return `${Latex.variableToLatex(this.name)}:\\begin{array}{llll}${this.definitionDomain.latexMarkup} & \\rightarrow & ${this.destinationDomain.latexMarkup}\\\\x & \\mapsto & ${this.expression.latexMarkup}\\end{array}`
} else {
return `\\begin{array}{l}${Latex.variableToLatex(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)
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, ctx) {
Function.drawFunction(canvas, ctx, this.expression, this.definitionDomain, this.destinationDomain, this.drawPoints, this.drawDashedLines)
// Label
var text = this.getLabel()
ctx.font = `${canvas.textsize}px sans-serif`
var textSize = canvas.measureText(ctx, text)
var posX = canvas.x2px(this.labelX)
var posY = canvas.y2px(this.execute(this.labelX))
let drawLabel = function(canvas, ctx, ltxImg) {
switch(this.labelPosition) {
case 'above':
canvas.drawVisibleImage(ctx, ltxImg.source, posX-ltxImg.width/2, posY-(ltxImg.height+10), ltxImg.width, ltxImg.height)
break;
case 'below':
canvas.drawVisibleImage(ctx, ltxImg.source, posX-ltxImg.width/2, posY+10, ltxImg.width, ltxImg.height)
break;
case 'left':
canvas.drawVisibleImage(ctx, ltxImg.source, posX-(ltxImg.width+10), posY-ltxImg.height/2, ltxImg.width, ltxImg.height)
break;
case 'right':
canvas.drawVisibleImage(ctx, ltxImg.source, posX+10, posY-ltxImg.height/2, ltxImg.width, ltxImg.height)
break;
case 'above-left':
canvas.drawVisibleImage(ctx, ltxImg.source, posX-(ltxImg.width+10), posY-(ltxImg.height+10), ltxImg.width, ltxImg.height)
break;
case 'above-right':
canvas.drawVisibleImage(ctx, ltxImg.source, posX+10, posY-(ltxImg.height+10), ltxImg.width, ltxImg.height)
break;
case 'below-left':
canvas.drawVisibleImage(ctx, ltxImg.source, posX-(ltxImg.width+10), posY+10, ltxImg.width, ltxImg.height)
break;
case 'below-right':
canvas.drawVisibleImage(ctx, ltxImg.source, posX+10, posY+10, ltxImg.width, ltxImg.height)
break;
}
}
let ltxLabel = this.getLatexLabel();
if(ltxLabel != "")
canvas.renderLatexImage(ltxLabel, this.color, drawLabel.bind(this))
//canvas.drawVisibleImage(ctx, ltxImg.source, posX, posY)
/*switch(this.labelPosition) {
case 'above':
canvas.drawVisibleText(ctx, text, posX-textSize.width/2, posY-textSize.height)
break;
case 'below':
canvas.drawVisibleText(ctx, text, posX-textSize.width/2, posY+textSize.height)
break;
case 'left':
canvas.drawVisibleText(ctx, text, posX-textSize.width, posY-textSize.height/2)
break;
case 'right':
canvas.drawVisibleText(ctx, text, posX, posY-textSize.height/2)
break;
case 'above-left':
canvas.drawVisibleText(ctx, text, posX-textSize.width, posY-textSize.height)
break;
case 'above-right':
canvas.drawVisibleText(ctx, text, posX, posY-textSize.height)
break;
case 'below-left':
canvas.drawVisibleText(ctx, text, posX-textSize.width, posY+textSize.height)
break;
case 'below-right':
canvas.drawVisibleText(ctx, text, posX, posY+textSize.height)
break;
}*/
}
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) {
// 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) {
// Reconverted for canvas to fix for logarithmic scales.
var currentX = definitionDomain.next(canvas.px2x(canvas.x2px(previousX)+pxprecision));
var 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))
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)
}
}
previousX = currentX
previousY = currentY
}
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)
}
} 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))
}
previousX = currentX
previousY = currentY
}
}
}
}