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();
|
||||
}
|
||||
|
||||
|
||||
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 }
|
||||
|
||||
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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue