From ef87b8362c88de4a2fc3de6f976ac2e4c225e754 Mon Sep 17 00:00:00 2001 From: Ad5001 Date: Fri, 25 Dec 2020 19:30:19 +0100 Subject: [PATCH] =?UTF-8?q?New=20special=20domains=20(=E2=84=95,=20?= =?UTF-8?q?=E2=84=A4=20and=20the=20likes),=20working=20with=20functions!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qml/LogGraphCanvas.qml | 12 ++- qml/js/mathlib.js | 206 ++++++++++++++++++++++++++++++++++------- qml/js/objects.js | 54 ++++++++--- 3 files changed, 225 insertions(+), 47 deletions(-) diff --git a/qml/LogGraphCanvas.qml b/qml/LogGraphCanvas.qml index 0d87480..f82c238 100644 --- a/qml/LogGraphCanvas.qml +++ b/qml/LogGraphCanvas.qml @@ -209,5 +209,15 @@ Canvas { ctx.stroke(); } - + function drawDashedLine(ctx, x1, y1, x2, y2, dashPxSize = 10) { + var distance = Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)) + var progPerc = dashPxSize/distance + ctx.beginPath(); + ctx.moveTo(x1, y1); + for(var i = progPerc/2; i < 1; i += progPerc) { + ctx.lineTo(x1-(x1-x2)*i, y1-(y1-y2)*i) + ctx.moveTo(x1-(x1-x2)*(i+progPerc/2), y1-(y1-y2)*(i+progPerc/2)) + } + ctx.stroke(); + } } diff --git a/qml/js/mathlib.js b/qml/js/mathlib.js index e54166d..b2ead97 100644 --- a/qml/js/mathlib.js +++ b/qml/js/mathlib.js @@ -89,8 +89,6 @@ class Domain { intersection(domain) { return this } - static import(frm) { return new EmptySet() } - static import(frm) { switch(frm.trim().toUpperCase()) { case "R": @@ -128,8 +126,50 @@ class Domain { case "ℝ*⁻": return Domain.RME break; + case "ℕ": + case "N": + case "ZP": + case "ℤ⁺": + return Domain.N + break; + case "NE": + case "NP": + case "N*": + case "N+": + case "ℕ*": + case "ℕ⁺": + case "ZPE": + case "ZEP": + case "Z+*": + case "Z*+": + case "ℤ⁺*": + case "ℤ*⁺": + return Domain.NE + break; + case "Z": + case "ℤ": + return Domain.Z + break; + case "ZM": + case "Z-": + case "ℤ⁻": + return Domain.ZM + break; + case "ZME": + case "ZEM": + case "Z-*": + case "Z*-": + case "ℤ⁻*": + case "ℤ*⁻": + return Domain.ZME + break; + case "ZE": + case "Z*": + case "ℤ*": + return Domain.ZE + break; default: - return EmptySet() + return new EmptySet() break; } } @@ -195,25 +235,76 @@ class Interval extends Domain { return new Interval(begin.trim(), end.trim(), openBegin, openEnd) } } -Domain.R = new Interval(-Infinity,Infinity,true,true) -Domain.R.displayName = "ℝ" -Domain.RP = new Interval(0,Infinity,true,false) -Domain.RP.displayName = "ℝ⁺" -Domain.RM = new Interval(-Infinity,0,true,false) -Domain.RM.displayName = "ℝ⁻" -Domain.RPE = new Interval(0,Infinity,true,true) -Domain.RPE.displayName = "ℝ⁺*" -Domain.RME = new Interval(-Infinity,0,true,true) -Domain.RME.displayName = "ℝ⁻*" -class DomainSet extends Domain { - constructor(values) { +class SpecialDomain extends Domain { + // For special domains (N, Z...) + // isValidExpr is function returning true when number is in domain + // false when it isn't. + // nextValue provides the next positive value in the domain + // after the one given. + constructor(displayName, isValid, next = x => true, previous = x => true, + moveSupported = true) { super() - var newVals = [] - values.forEach(function(value){ - newVals.push(new Expression(value.toString())) - }) - this.values = newVals + this.displayName = displayName + this.isValid = isValid + this.nextValue = next + this.prevValue = previous + this.moveSupported = moveSupported + } + + includes(x) { + if(typeof x == 'string') x = executeExpression(x) + return this.isValid(x) + } + + next(x) { + if(typeof x == 'string') x = executeExpression(x) + return this.nextValue(x) + } + + previous(x) { + if(typeof x == 'string') x = executeExpression(x) + return this.prevValue(x) + } + + toString() { + return this.displayName + } + + union(domain) { + if(domain instanceof EmptySet) return this + if(domain instanceof DomainSet) return domain.union(this) + if(domain instanceof UnionDomain) return new UnionDomain(this, domain) + if(domain instanceof IntersectionDomain) return new UnionDomain(this, domain) + if(domain instanceof MinusDomain) return new UnionDomain(this, domain) + if(domain instanceof Interval) return new UnionDomain(this, domain) + } + + intersection(domain) { + if(domain instanceof EmptySet) return domain + if(domain instanceof DomainSet) return domain.intersection(this) + if(domain instanceof UnionDomain) return new IntersectionDomain(this, domain) + if(domain instanceof IntersectionDomain) return new IntersectionDomain(this, domain) + if(domain instanceof MinusDomain) return new IntersectionDomain(this, domain) + if(domain instanceof Interval) return new IntersectionDomain(this, domain) + } +} + + +class DomainSet extends SpecialDomain { + constructor(values) { + super('', x => true, x => x, true) + console.log(values) + var newVals = {} + this.executedValues = [] + for(var i = 0; i < values.length; i++) { + var expr = new Expression(values[i].toString()) + var ex = expr.execute() + newVals[ex] = expr + this.executedValues.push(ex) + } + this.executedValues.sort((a,b) => a-b) + this.values = this.executedValues.map(val => newVals[val]) } includes(x) { @@ -223,6 +314,29 @@ class DomainSet extends Domain { return false } + next(x) { + if(typeof x == 'string') x = executeExpression(x) + if(x < this.executedValues[0]) return this.executedValues[0] + for(var i = 1; i < this.values.length; i++) { + var prevValue = this.executedValues[i-1] + var value = this.executedValues[i] + if(x >= prevValue && x < value) return value + } + return null + } + + previous(x) { + if(typeof x == 'string') x = executeExpression(x) + if(x > this.executedValues[this.executedValues.length-1]) + return this.executedValues[this.executedValues.length-1] + for(var i = 1; i < this.values.length; i++) { + var prevValue = this.executedValues[i-1] + var value = this.executedValues[i] + if(x > prevValue && x <= value) return prevValue + } + return null + } + toString() { return "{" + this.values.join(";") + "}" } @@ -239,7 +353,7 @@ class DomainSet extends Domain { } var notIncludedValues = [] for(var i = 0; i < this.values.length; i++) { - var value = this.values[i].execute() + var value = this.executedValues[i] if(domain instanceof Interval) { if(domain.begin.execute() == value && domain.openBegin) { domain.openBegin = false @@ -266,7 +380,7 @@ class DomainSet extends Domain { } var includedValues = [] for(var i = 0; i < this.values.length; i++) { - var value = this.values[i].execute() + var value = this.executedValues[i] if(domain instanceof Interval) { if(domain.begin.execute() == value && !domain.openBegin) { domain.openBegin = false @@ -325,7 +439,6 @@ class UnionDomain extends Domain { if(domains.length == 1) domains = frm.trim().split("U") // Fallback var dom1 = parseDomain(domains.pop()) var dom2 = parseDomain(domains.join('∪')) - console.log('Union', dom1, dom2) return dom1.union(dom2) } } @@ -365,7 +478,7 @@ class IntersectionDomain extends Domain { static import(frm) { var domains = frm.trim().split("∩") var dom1 = parseDomain(domains.pop()) - var dom2 = parseDomain(domains.join('∪')) + var dom2 = parseDomain(domains.join('∩')) return dom1.intersection(dom2) } } @@ -397,14 +510,41 @@ class MinusDomain extends Domain { Domain.RE = new MinusDomain("R", "{0}") Domain.RE.displayName = "ℝ*" +Domain.R = new Interval(-Infinity,Infinity,true,true) +Domain.R.displayName = "ℝ" +Domain.RP = new Interval(0,Infinity,true,false) +Domain.RP.displayName = "ℝ⁺" +Domain.RM = new Interval(-Infinity,0,true,false) +Domain.RM.displayName = "ℝ⁻" +Domain.RPE = new Interval(0,Infinity,true,true) +Domain.RPE.displayName = "ℝ⁺*" +Domain.RME = new Interval(-Infinity,0,true,true) +Domain.RME.displayName = "ℝ⁻*" +Domain.N = new SpecialDomain('ℕ', x => x%1==0 && x >= 0, + x => Math.max(Math.floor(x)+1, 0), + x => Math.max(Math.ceil(x)-1, 0)) +Domain.NE = new SpecialDomain('ℕ*', x => x%1==0 && x > 0, + x => Math.max(Math.floor(x)+1, 1), + x => Math.max(Math.ceil(x)-1, 1)) +Domain.Z = new SpecialDomain('ℤ', x => x%1==0, x => Math.floor(x)+1, x => Math.ceil(x)-1) +Domain.ZE = new SpecialDomain('ℤ*', x => x%1==0 && x != 0, + x => Math.floor(x)+1 == 0 ? Math.floor(x)+2 : Math.floor(x)+1, + x => Math.ceil(x)-1 == 0 ? Math.ceil(x)-2 : Math.ceil(x)-1) +Domain.ZM = new SpecialDomain('ℤ⁻', x => x%1==0 && x <= 0, + x => Math.min(Math.floor(x)+1, 0), + x => Math.min(Math.ceil(x)-1, 0)) +Domain.ZME = new SpecialDomain('ℤ⁻*', x => x%1==0 && x < 0, + x => Math.min(Math.floor(x)+1, -1), + x => Math.min(Math.ceil(x)-1, -1)) + + var refedDomains = [] function parseDomain(domain) { if(!domain.includes(')') && !domain.includes('(')) return parseDomainSimple(domain) var domStr while((domStr = /\(([^)(]+)\)/.exec(domain)) !== null) { - var dom = parseDomainSimple(domStr[1]); - console.log(domain, domStr[0]) + var dom = parseDomainSimple(domStr[1].trim()); domain = domain.replace(domStr[0], 'D' + refedDomains.length) refedDomains.push(dom) } @@ -412,12 +552,14 @@ function parseDomain(domain) { } function parseDomainSimple(domain) { - if(domain[0] == 'D') return refedDomains[parseInt(domain.substr(1))] - if(domain.indexOf("U") >= 0 || domain.indexOf("∪") >= 0) return UnionDomain.import(domain) - if(domain.indexOf("∩") >= 0) return IntersectionDomain.import(domain) - if(domain.indexOf("∖") >= 0 || domain.indexOf("\\") >= 0) return MinusDomain.import(domain) + domain = domain.trim() + if(domain.includes("U") || domain.includes("∪")) return UnionDomain.import(domain) + if(domain.includes("∩")) return IntersectionDomain.import(domain) + if(domain.includes("∖") || domain.includes("\\")) return MinusDomain.import(domain) if(domain.charAt(0) == "{" && domain.charAt(domain.length -1) == "}") return DomainSet.import(domain) - if(domain.indexOf("]") >= 0 || domain.indexOf("[") >= 0) return Interval.import(domain) - if(domain.toUpperCase().indexOf("R") >= 0 || domain.indexOf("ℝ") >= 0) return Domain.import(domain) + if(domain.includes("]") || domain.includes("[")) return Interval.import(domain) + if(["R", "ℝ", "N", "ℕ", "Z", "ℤ"].some(str => domain.toUpperCase().includes(str))) + return Domain.import(domain) + if(domain[0] == 'D') return refedDomains[parseInt(domain.substr(1))] return new EmptySet() } diff --git a/qml/js/objects.js b/qml/js/objects.js index bfddabd..a52c89d 100644 --- a/qml/js/objects.js +++ b/qml/js/objects.js @@ -158,8 +158,8 @@ class Point extends DrawableObject { canvas.drawLine(ctx, canvasX-pointSize/2, canvasY+pointSize/2, canvasX+pointSize/2, canvasY-pointSize/2) break; case '+': - canvas.drawLine(ctx, canvasX, canvasY-pointSize/2, canvasX, canvasY+pointSize/2) - canvas.drawLine(ctx, canvasX-pointSize/2, canvasY, canvasX+pointSize/2, canvasY) + ctx.fillRect(canvasX-pointSize/2, canvasY-1, pointSize, 2) + ctx.fillRect(canvasX-1, canvasY-pointSize/2, 2, pointSize) break; } var text = this.getLabel() @@ -265,10 +265,10 @@ class Function extends ExecutableObject { var posY = canvas.y2px(this.expression.execute(this.labelX)) switch(this.labelPosition) { case 'above': - canvas.drawVisibleText(ctx, text, posX-textSize.width/2, posY-textSize.height) + canvas.drawVisibleText(ctx, text, posX-textSize.width, posY-textSize.height) break; case 'below': - canvas.drawVisibleText(ctx, text, posX-textSize.width/2, posY+textSize.height) + canvas.drawVisibleText(ctx, text, posX-textSize.width, posY+textSize.height) break; } @@ -279,17 +279,43 @@ class Function extends ExecutableObject { // Drawing small traits every 2px var pxprecision = 2 var previousX = canvas.px2x(0) - var 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((inDomain.includes(currentX) || inDomain.includes(previousX)) && - (outDomain.includes(currentY) || outDomain.includes(previousY)) && - Math.abs(previousY-currentY)<100) { // 100 per 2px is a lot (probably inf to inf issue) - canvas.drawLine(ctx, canvas.x2px(previousX), canvas.y2px(previousY), canvas.x2px(currentX), canvas.y2px(currentY)) + var previousY; + var draw = function(currentX) { + } + console.log('Drawing', inDomain, inDomain instanceof MathLib.SpecialDomain) + if(inDomain instanceof MathLib.SpecialDomain && inDomain.moveSupported) { + previousX = inDomain.previous(previousX) + if(previousX === null) previousX = inDomain.next(canvas.px2x(0)) + previousY = expr.execute(previousX) + while(previousX !== null && canvas.x2px(previousX) < canvas.canvasSize.width) { + var currentX = inDomain.next(previousX) + var currentY = expr.execute(currentX) + if(currentX === null) break; + if((inDomain.includes(currentX) || inDomain.includes(previousX)) && + (outDomain.includes(currentY) || outDomain.includes(previousY))) { + canvas.drawDashedLine(ctx, canvas.x2px(previousX), canvas.y2px(previousY), canvas.x2px(currentX), canvas.y2px(currentY)) + 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 + previousX = currentX + previousY = currentY + } + // 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((inDomain.includes(currentX) || inDomain.includes(previousX)) && + (outDomain.includes(currentY) || outDomain.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 + } } } }