From 166d1a2485a26b4cafd3306eb1c66325cd5a7097 Mon Sep 17 00:00:00 2001 From: Ad5001 Date: Mon, 7 Mar 2022 17:20:24 +0100 Subject: [PATCH] X Cursor Latex implementation Also a few bugfixes and added documentation --- .../LogarithmPlotter/LogGraphCanvas.qml | 8 ++ .../LogarithmPlotter/js/math/expression.js | 2 +- .../ad5001/LogarithmPlotter/js/math/latex.js | 4 +- .../LogarithmPlotter/js/math/sequence.js | 2 +- .../ad5001/LogarithmPlotter/js/objs/common.js | 87 ++++++++------ .../LogarithmPlotter/js/objs/function.js | 2 +- .../LogarithmPlotter/js/objs/xcursor.js | 112 ++++++++---------- 7 files changed, 117 insertions(+), 100 deletions(-) diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogGraphCanvas.qml b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogGraphCanvas.qml index 19b82e9..2e50d88 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogGraphCanvas.qml +++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogGraphCanvas.qml @@ -157,7 +157,15 @@ Canvas { */ property int drawMaxX: Math.ceil(Math.max(Math.abs(xmin), Math.abs(px2x(canvasSize.width)))/xaxisstep1) + /*! + \qmlproperty var LogGraphCanvas::imageLoaders + Dictionary of format {image: [callback.image data]} containing data for defered image loading. + */ property var imageLoaders: {} + /*! + \qmlproperty var LogGraphCanvas::ctx + Cache for the 2D context so that it may be used asynchronously. + */ property var ctx Component.onCompleted: imageLoaders = {} diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/expression.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/expression.js index ed2bee9..58c4528 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/expression.js +++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/expression.js @@ -31,7 +31,7 @@ class Expression { this.calc = C.parser.parse(expr).simplify() this.cached = this.isConstant() this.cachedValue = this.cached ? this.calc.evaluate(C.evalVariables) : null - this.latexMarkup = Latex.expressionToLatex(this.calc.tokens) + this.latexMarkup = Latex.expression(this.calc.tokens) } isConstant() { diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/latex.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/latex.js index 5d73a16..d5b30e6 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/latex.js +++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/latex.js @@ -125,7 +125,7 @@ function variable(vari) { * @param {Array} tokens - expr-eval tokens list * @returns {string} */ -function expressionToLatex(tokens) { +function expression(tokens) { var nstack = []; var n1, n2, n3; var f, args, argCount; @@ -239,7 +239,7 @@ function expressionToLatex(tokens) { nstack.push('[' + args.join(', ') + ']'); break; case ExprEval.IEXPR: - nstack.push('(' + expressionToLatex(item.value) + ')'); + nstack.push('(' + expression(item.value) + ')'); break; case ExprEval.IENDSTATEMENT: break; diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/sequence.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/sequence.js index ff97760..cdee74c 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/sequence.js +++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/sequence.js @@ -38,7 +38,7 @@ class Sequence extends Expr.Expression { for(var n in this.calcValues) if(['string', 'number'].includes(typeof this.calcValues[n])) { let parsed = C.parser.parse(this.calcValues[n].toString()).simplify() - this.latexValues[n] = Latex.expressionToLatex(parsed.tokens) + this.latexValues[n] = Latex.expression(parsed.tokens) this.calcValues[n] = parsed.evaluate(C.evalVariables) } this.valuePlus = parseInt(valuePlus) diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/common.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/common.js index 599766d..956ecf6 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/common.js +++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/common.js @@ -209,45 +209,12 @@ class DrawableObject { */ draw(canvas, ctx) {} - - /** - * Automaticly draw the label of the object on the \c canvas with the 2D context \c ctx. - * 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. - * @param {Canvas} canvas - * @param {Context2D} ctx - * @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. - */ - drawLabel(canvas, ctx, labelPosition, posX, posY, forceText = false) { - let offset - if(!forceText && true) { // TODO: Check for user setting with Latex. - // With latex - let drawLblCb = function(canvas, ctx, ltxImg) { - this.drawLabelDivergence(labelPosition, 8, ltxImg, posX, posY, - (x,y) => canvas.drawVisibleImage(ctx, ltxImg.source, x, y, ltxImg.width, ltxImg.height)) - } - let ltxLabel = this.getLatexLabel(); - if(ltxLabel != "") - canvas.renderLatexImage(ltxLabel, this.color, drawLblCb.bind(this)) - //canvas.drawVisibleImage(ctx, ltxImg.source, posX, posY) - } else { - // Without latex - let text = this.getLabel() - ctx.font = `${canvas.textsize}px sans-serif` - this.drawLabelDivergence(labelPosition, 4, canvas.measureText(ctx, text), posX, posY, - (x,y) => canvas.drawVisibleText(ctx, text, x, y)) - } - } - - /** * 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 * 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" @@ -255,7 +222,7 @@ class DrawableObject { * @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 */ - drawLabelDivergence(labelPosition, offset, size, posX, posY, drawFunction) { + drawPositionDivergence(labelPosition, offset, size, posX, posY, drawFunction) { switch(labelPosition) { case 'center': drawFunction(posX-size.width/2, posY-size.height/2) @@ -293,6 +260,56 @@ class DrawableObject { } } + /** + * Automaticly 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 + * 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. + * + * @param {Canvas} canvas + * @param {Context2D} ctx + * @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 {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) to display the text + */ + drawLabel(canvas, ctx, 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(ctx, ltxImg.source, x, y, ltxImg.width, ltxImg.height) + if(drawFunctionText == null) + drawFunctionText = (x,y,text) => canvas.drawVisibleText(ctx, text, x, textSize.height+5) + // Drawing the label + let offset + if(!forceText && true) { // TODO: Check for user setting with Latex. + // With latex + let drawLblCb = function(canvas, ctx, ltxImg) { + this.drawPositionDivergence(labelPosition, 8, ltxImg, posX, posY, (x,y) => drawFunctionLatex(x,y,ltxImg)) + } + let ltxLabel = getLatexFunction(); + if(ltxLabel != "") + canvas.renderLatexImage(ltxLabel, this.color, drawLblCb.bind(this)) + } else { + // Without latex + let text = getTextFunction() + ctx.font = `${canvas.textsize}px sans-serif` + this.drawPositionDivergence(labelPosition, 4, canvas.measureText(ctx, text), posX, posY, (x,y) => drawFunctionText(x,y,text)) + } + } + toString() { return this.name; } diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/function.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/function.js index b1a98b8..44dc1f4 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/function.js +++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/function.js @@ -81,7 +81,7 @@ class Function extends Common.ExecutableObject { 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}\\\\ textD_{${this.name}} = ${this.definitionDomain.latexMarkup}\\end{array}` + return `\\begin{array}{l}${Latex.variable(this.name)}(x) = ${this.expression.latexMarkup}\\\\ D_{${this.name}} = ${this.definitionDomain.latexMarkup}\\end{array}` } } diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/xcursor.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/xcursor.js index e81c1a3..56c6fe4 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/xcursor.js +++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/xcursor.js @@ -22,27 +22,14 @@ .import "../objects.js" as Objects .import "../mathlib.js" as MathLib .import "../parameters.js" as P +.import "../math/latex.js" as Latex + class XCursor extends Common.DrawableObject { static type(){return 'X Cursor'} static displayType(){return qsTr('X Cursor')} static displayTypeMultiple(){return qsTr('X Cursors')} - /*static properties() { - return { - 'x': 'Expression', - 'targetElement': new P.ObjectType('ExecutableObject'), - 'labelPosition': new P.Enum('left', 'right'), - 'approximate': 'boolean', - 'rounding': 'number', - 'displayStyle': new P.Enum( - '— — — — — — —', - '⸺⸺⸺⸺⸺⸺', - '• • • • • • • • • •' - ), - 'targetValuePosition' : new P.Enum('Next to target', 'With label', 'Hidden') - } - }*/ static properties() {return { [QT_TRANSLATE_NOOP('prop','x')]: 'Expression', [QT_TRANSLATE_NOOP('prop','targetElement')]: new P.ObjectType('ExecutableObject'), @@ -87,6 +74,13 @@ class XCursor extends Common.DrawableObject { 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()}` + } + getTargetValueLabel() { var t = this.targetElement var approx = '' @@ -98,6 +92,18 @@ class XCursor extends Common.DrawableObject { (this.approximate ? ' ≈ ' + approx : '') } + getTargetValueLatexLabel() { + var t = this.targetElement + var approx = '' + if(this.approximate) { + approx = t.execute(this.x.execute()) + approx = approx.toPrecision(this.rounding + Math.round(approx).toString().length) + } + let simpl = t.simplify(this.x.toEditableString()) + return `${Latex.variable(t.name)}(${Latex.variable(this.name)}) = ${simpl.tokens ? Latex.expression(simpl.tokens) : simpl}` + + (this.approximate ? ' \\simeq ' + approx : '') + } + getLabel() { switch(this.labelContent) { case 'name': @@ -118,8 +124,28 @@ class XCursor extends Common.DrawableObject { } } + getLatexLabel() { + switch(this.labelContent) { + case 'name': + return Latex.variable(this.name) + break; + case 'name + value': + switch(this.targetValuePosition) { + case 'Next to target': + case 'Hidden': + return `${Latex.variable(this.name)} = ${this.x.latexMarkup}` + break; + case 'With label': + return this.getLatexString() + break; + } + case 'null': + return '' + } + } + draw(canvas, ctx) { - var xpos = canvas.x2px(this.x.execute()) + let xpos = canvas.x2px(this.x.execute()) switch(this.displayStyle) { case '— — — — — — —': var dashPxSize = 10 @@ -139,52 +165,18 @@ class XCursor extends Common.DrawableObject { break; } - // Label - var text = this.getLabel() - ctx.font = `${canvas.textsize}px sans-serif` - var textSize = canvas.measureText(ctx, text) - - switch(this.labelPosition) { - case 'left': - case 'above-left': - case 'below-left': - case 'below': - case 'above': - canvas.drawVisibleText(ctx, text, xpos-textSize.width-5, textSize.height+5) - break; - case 'right': - case 'above-right': - case 'below-right': - canvas.drawVisibleText(ctx, text, xpos+5, textSize.height+5) - 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) => canvas.drawVisibleText(ctx, text, x, textSize.height+5)) + // Drawing label at the position of the target element. if(this.targetValuePosition == 'Next to target' && this.targetElement != null) { - var text = this.getTargetValueLabel() - var textSize = canvas.measureText(ctx, text) - var ypox = canvas.y2px(this.targetElement.execute(this.x.execute())) - switch(this.labelPosition) { - case 'left': - case 'below': - case 'above': - canvas.drawVisibleText(ctx, text, xpos-textSize.width-5, ypox+textSize.height) - break; - case 'above-left': - canvas.drawVisibleText(ctx, text, xpos-textSize.width-5, ypox+textSize.height+12) - break; - case 'below-left': - canvas.drawVisibleText(ctx, text, xpos-textSize.width-5, ypox+textSize.height-12) - break; - case 'right': - canvas.drawVisibleText(ctx, text, xpos+5, ypox+textSize.height) - break; - case 'above-right': - canvas.drawVisibleText(ctx, text, xpos+5, ypox+textSize.height+12) - break; - case 'below-right': - canvas.drawVisibleText(ctx, text, xpos+5, ypox+textSize.height-12) - break; - } + let ypos = canvas.y2px(this.targetElement.execute(this.x.execute())) + this.drawLabel(canvas, ctx, 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) => canvas.drawVisibleText(ctx, text, x, y)) } } }