New special domains (ℕ, ℤ and the likes), working with functions!
This commit is contained in:
parent
065b88bfd0
commit
ef87b8362c
3 changed files with 225 additions and 47 deletions
|
@ -209,5 +209,15 @@ Canvas {
|
||||||
ctx.stroke();
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,8 +89,6 @@ class Domain {
|
||||||
|
|
||||||
intersection(domain) { return this }
|
intersection(domain) { return this }
|
||||||
|
|
||||||
static import(frm) { return new EmptySet() }
|
|
||||||
|
|
||||||
static import(frm) {
|
static import(frm) {
|
||||||
switch(frm.trim().toUpperCase()) {
|
switch(frm.trim().toUpperCase()) {
|
||||||
case "R":
|
case "R":
|
||||||
|
@ -128,8 +126,50 @@ class Domain {
|
||||||
case "ℝ*⁻":
|
case "ℝ*⁻":
|
||||||
return Domain.RME
|
return Domain.RME
|
||||||
break;
|
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:
|
default:
|
||||||
return EmptySet()
|
return new EmptySet()
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,25 +235,76 @@ class Interval extends Domain {
|
||||||
return new Interval(begin.trim(), end.trim(), openBegin, openEnd)
|
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 {
|
class SpecialDomain extends Domain {
|
||||||
constructor(values) {
|
// 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()
|
super()
|
||||||
var newVals = []
|
this.displayName = displayName
|
||||||
values.forEach(function(value){
|
this.isValid = isValid
|
||||||
newVals.push(new Expression(value.toString()))
|
this.nextValue = next
|
||||||
})
|
this.prevValue = previous
|
||||||
this.values = newVals
|
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) {
|
includes(x) {
|
||||||
|
@ -223,6 +314,29 @@ class DomainSet extends Domain {
|
||||||
return false
|
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() {
|
toString() {
|
||||||
return "{" + this.values.join(";") + "}"
|
return "{" + this.values.join(";") + "}"
|
||||||
}
|
}
|
||||||
|
@ -239,7 +353,7 @@ class DomainSet extends Domain {
|
||||||
}
|
}
|
||||||
var notIncludedValues = []
|
var notIncludedValues = []
|
||||||
for(var i = 0; i < this.values.length; i++) {
|
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 instanceof Interval) {
|
||||||
if(domain.begin.execute() == value && domain.openBegin) {
|
if(domain.begin.execute() == value && domain.openBegin) {
|
||||||
domain.openBegin = false
|
domain.openBegin = false
|
||||||
|
@ -266,7 +380,7 @@ class DomainSet extends Domain {
|
||||||
}
|
}
|
||||||
var includedValues = []
|
var includedValues = []
|
||||||
for(var i = 0; i < this.values.length; i++) {
|
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 instanceof Interval) {
|
||||||
if(domain.begin.execute() == value && !domain.openBegin) {
|
if(domain.begin.execute() == value && !domain.openBegin) {
|
||||||
domain.openBegin = false
|
domain.openBegin = false
|
||||||
|
@ -325,7 +439,6 @@ class UnionDomain extends Domain {
|
||||||
if(domains.length == 1) domains = frm.trim().split("U") // Fallback
|
if(domains.length == 1) domains = frm.trim().split("U") // Fallback
|
||||||
var dom1 = parseDomain(domains.pop())
|
var dom1 = parseDomain(domains.pop())
|
||||||
var dom2 = parseDomain(domains.join('∪'))
|
var dom2 = parseDomain(domains.join('∪'))
|
||||||
console.log('Union', dom1, dom2)
|
|
||||||
return dom1.union(dom2)
|
return dom1.union(dom2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -365,7 +478,7 @@ class IntersectionDomain extends Domain {
|
||||||
static import(frm) {
|
static import(frm) {
|
||||||
var domains = frm.trim().split("∩")
|
var domains = frm.trim().split("∩")
|
||||||
var dom1 = parseDomain(domains.pop())
|
var dom1 = parseDomain(domains.pop())
|
||||||
var dom2 = parseDomain(domains.join('∪'))
|
var dom2 = parseDomain(domains.join('∩'))
|
||||||
return dom1.intersection(dom2)
|
return dom1.intersection(dom2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -397,14 +510,41 @@ class MinusDomain extends Domain {
|
||||||
Domain.RE = new MinusDomain("R", "{0}")
|
Domain.RE = new MinusDomain("R", "{0}")
|
||||||
Domain.RE.displayName = "ℝ*"
|
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 = []
|
var refedDomains = []
|
||||||
|
|
||||||
function parseDomain(domain) {
|
function parseDomain(domain) {
|
||||||
if(!domain.includes(')') && !domain.includes('(')) return parseDomainSimple(domain)
|
if(!domain.includes(')') && !domain.includes('(')) return parseDomainSimple(domain)
|
||||||
var domStr
|
var domStr
|
||||||
while((domStr = /\(([^)(]+)\)/.exec(domain)) !== null) {
|
while((domStr = /\(([^)(]+)\)/.exec(domain)) !== null) {
|
||||||
var dom = parseDomainSimple(domStr[1]);
|
var dom = parseDomainSimple(domStr[1].trim());
|
||||||
console.log(domain, domStr[0])
|
|
||||||
domain = domain.replace(domStr[0], 'D' + refedDomains.length)
|
domain = domain.replace(domStr[0], 'D' + refedDomains.length)
|
||||||
refedDomains.push(dom)
|
refedDomains.push(dom)
|
||||||
}
|
}
|
||||||
|
@ -412,12 +552,14 @@ function parseDomain(domain) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDomainSimple(domain) {
|
function parseDomainSimple(domain) {
|
||||||
if(domain[0] == 'D') return refedDomains[parseInt(domain.substr(1))]
|
domain = domain.trim()
|
||||||
if(domain.indexOf("U") >= 0 || domain.indexOf("∪") >= 0) return UnionDomain.import(domain)
|
if(domain.includes("U") || domain.includes("∪")) return UnionDomain.import(domain)
|
||||||
if(domain.indexOf("∩") >= 0) return IntersectionDomain.import(domain)
|
if(domain.includes("∩")) return IntersectionDomain.import(domain)
|
||||||
if(domain.indexOf("∖") >= 0 || domain.indexOf("\\") >= 0) return MinusDomain.import(domain)
|
if(domain.includes("∖") || domain.includes("\\")) return MinusDomain.import(domain)
|
||||||
if(domain.charAt(0) == "{" && domain.charAt(domain.length -1) == "}") return DomainSet.import(domain)
|
if(domain.charAt(0) == "{" && domain.charAt(domain.length -1) == "}") return DomainSet.import(domain)
|
||||||
if(domain.indexOf("]") >= 0 || domain.indexOf("[") >= 0) return Interval.import(domain)
|
if(domain.includes("]") || domain.includes("[")) return Interval.import(domain)
|
||||||
if(domain.toUpperCase().indexOf("R") >= 0 || domain.indexOf("ℝ") >= 0) return Domain.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()
|
return new EmptySet()
|
||||||
}
|
}
|
||||||
|
|
|
@ -158,8 +158,8 @@ class Point extends DrawableObject {
|
||||||
canvas.drawLine(ctx, canvasX-pointSize/2, canvasY+pointSize/2, canvasX+pointSize/2, canvasY-pointSize/2)
|
canvas.drawLine(ctx, canvasX-pointSize/2, canvasY+pointSize/2, canvasX+pointSize/2, canvasY-pointSize/2)
|
||||||
break;
|
break;
|
||||||
case '+':
|
case '+':
|
||||||
canvas.drawLine(ctx, canvasX, canvasY-pointSize/2, canvasX, canvasY+pointSize/2)
|
ctx.fillRect(canvasX-pointSize/2, canvasY-1, pointSize, 2)
|
||||||
canvas.drawLine(ctx, canvasX-pointSize/2, canvasY, canvasX+pointSize/2, canvasY)
|
ctx.fillRect(canvasX-1, canvasY-pointSize/2, 2, pointSize)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
var text = this.getLabel()
|
var text = this.getLabel()
|
||||||
|
@ -265,10 +265,10 @@ class Function extends ExecutableObject {
|
||||||
var posY = canvas.y2px(this.expression.execute(this.labelX))
|
var posY = canvas.y2px(this.expression.execute(this.labelX))
|
||||||
switch(this.labelPosition) {
|
switch(this.labelPosition) {
|
||||||
case 'above':
|
case 'above':
|
||||||
canvas.drawVisibleText(ctx, text, posX-textSize.width/2, posY-textSize.height)
|
canvas.drawVisibleText(ctx, text, posX-textSize.width, posY-textSize.height)
|
||||||
break;
|
break;
|
||||||
case 'below':
|
case 'below':
|
||||||
canvas.drawVisibleText(ctx, text, posX-textSize.width/2, posY+textSize.height)
|
canvas.drawVisibleText(ctx, text, posX-textSize.width, posY+textSize.height)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -279,19 +279,45 @@ class Function extends ExecutableObject {
|
||||||
// Drawing small traits every 2px
|
// Drawing small traits every 2px
|
||||||
var pxprecision = 2
|
var pxprecision = 2
|
||||||
var previousX = canvas.px2x(0)
|
var previousX = canvas.px2x(0)
|
||||||
var previousY = expr.execute(previousX)
|
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
|
||||||
|
}
|
||||||
|
// 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) {
|
for(var px = pxprecision; px < canvas.canvasSize.width; px += pxprecision) {
|
||||||
var currentX = canvas.px2x(px)
|
var currentX = canvas.px2x(px)
|
||||||
var currentY = expr.execute(currentX)
|
var currentY = expr.execute(currentX)
|
||||||
if((inDomain.includes(currentX) || inDomain.includes(previousX)) &&
|
if((inDomain.includes(currentX) || inDomain.includes(previousX)) &&
|
||||||
(outDomain.includes(currentY) || outDomain.includes(previousY)) &&
|
(outDomain.includes(currentY) || outDomain.includes(previousY)) &&
|
||||||
Math.abs(previousY-currentY)<100) { // 100 per 2px is a lot (probably inf to inf issue)
|
Math.abs(previousY-currentY)<100) {
|
||||||
canvas.drawLine(ctx, canvas.x2px(previousX), canvas.y2px(previousY), canvas.x2px(currentX), canvas.y2px(currentY))
|
canvas.drawLine(ctx, canvas.x2px(previousX), canvas.y2px(previousY), canvas.x2px(currentX), canvas.y2px(currentY))
|
||||||
}
|
}
|
||||||
previousX = currentX
|
previousX = currentX
|
||||||
previousY = currentY
|
previousY = currentY
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue