@eclipse-scout/chart
Version:
Eclipse Scout chart
231 lines (202 loc) • 6.49 kB
text/typescript
/*
* Copyright (c) 2010, 2023 BSI Business Systems Integration AG
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
// place venn 3 by simulation
// find angle and distance (to a) and radius where "error" is minimal
import {VennCircle, VennCircleHelper} from '../index';
export class VennAsync3Calculator {
helper: VennCircleHelper;
venn1: VennCircle;
venn2: VennCircle;
venn3: VennCircle;
u: number;
v: number;
w: number;
uv: number;
uw: number;
vw: number;
uvw: number;
maxD: number;
dStep: number;
rStep: number;
alphaStep: number;
alphaBest: number;
dBest: number;
rBest: number;
errorBest: number;
callback: () => void;
cancelled: boolean;
constructor(helper: VennCircleHelper, venn1: VennCircle, venn2: VennCircle, venn3: VennCircle, u: number, v: number, w: number, uv: number, uw: number, vw: number, uvw: number, d12: number, d13: number, d23: number) {
// if circles are empty, they are drawn as small circle, so: adjust u v w to find better errors
if (u === 0 && uv === 0 && uw === 0 && uvw === 0) {
u = 1;
}
if (v === 0 && uv === 0 && vw === 0 && uvw === 0) {
v = 1;
}
if (w === 0 && uw === 0 && vw === 0 && uvw === 0) {
w = 1;
}
this.helper = helper;
this.venn1 = venn1;
this.venn2 = venn2;
this.venn3 = venn3;
this.u = u;
this.v = v;
this.w = w;
this.uv = uv;
this.uw = uw;
this.vw = vw;
this.uvw = uvw;
/** step and ranges for loops */
this.maxD = this.venn1.r + 2 * this.venn2.r + 2 * this.venn1.r + this.helper.distR;
this.dStep = this.maxD / 30;
this.rStep = venn3.r / 4;
this.alphaStep = Math.PI / 30;
/** best vars (initialize with 0 so the optimizer knows that they are numbers) */
this.alphaBest = 0;
this.dBest = 0;
this.rBest = 0;
this.errorBest = 0;
this.callback = null;
this.cancelled = false;
}
start(callback: () => void) {
this.callback = callback;
setTimeout(this._next.bind(this, 0));
}
cancel() {
this.cancelled = true;
}
protected _end() {
// set x and y and r of venn3
this.venn3.x = this.venn1.x + this.dBest * Math.cos(this.alphaBest);
this.venn3.y = this.venn1.y - this.dBest * Math.sin(this.alphaBest);
this.venn3.r = this.rBest;
this.callback();
}
protected _next(alpha: number) {
if (!this.cancelled) {
// iterate
this._iteration(alpha);
}
if (this.cancelled) {
return; // stop loop if interrupted
}
alpha += this.alphaStep;
if (alpha < Math.PI) {
// schedule next loop iteration
setTimeout(this._next.bind(this, alpha));
} else {
// end loop
this._end();
}
}
protected _iteration(alpha: number) {
// optimize speed: no var lookup (should help the optimizer in general, and IE in particular)
let maxD = this.maxD,
dStep = this.dStep,
minR = this.helper.minR,
rStep = this.rStep,
total = this.helper.total,
x1 = this.venn1.x,
y1 = this.venn1.y,
r1 = this.venn1.r,
x2 = this.venn2.x,
y2 = this.venn2.y,
r2 = this.venn2.r,
r3 = this.venn3.r,
u = this.u,
v = this.v,
w = this.w,
uv = this.uv,
uw = this.uw,
vw = this.vw,
uvw = this.uvw,
alphaBest = this.alphaBest,
dBest = this.dBest,
rBest = this.rBest,
errorBest = this.errorBest;
for (let d = 0; d < maxD; d += dStep) {
// calc x, y
let x = x1 + d * Math.cos(alpha);
let y = y1 - d * Math.sin(alpha);
for (let r = Math.max(minR, r3 * 0.75); r <= r3 * 1.25; r += rStep) {
// find areas with monte carlo, do not laugh! i tried even this:
// http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.507.1195&rep=rep1&type=pdf
let minX = Math.min(x1 - r1, x2 - r2, x - r);
let maxX = Math.max(x1 + r1, x2 + r2, x + r);
let minY = Math.min(y1 - r1, y2 - r2, y - r);
let maxY = Math.max(y1 + r1, y2 + r2, y + r);
let stepX = (maxX - minX) / 100;
let stepY = (maxY - minY) / 100;
// areas of venn
let a1 = 0,
a2 = 0,
a3 = 0,
a12 = 0,
a13 = 0,
a23 = 0,
a123 = 0;
for (let testX = minX; testX < maxX; testX += stepX) {
for (let testY = minY; testY < maxY; testY += stepY) {
// optimize speed for ie: no function call
let t1 = ((testX - x1) * (testX - x1) + (testY - y1) * (testY - y1)) < (r1 * r1);
let t2 = ((testX - x2) * (testX - x2) + (testY - y2) * (testY - y2)) < (r2 * r2);
let t3 = ((testX - x) * (testX - x) + (testY - y) * (testY - y)) < (r * r);
// check if inside
if (t1 && t2 && t3) {
a123++;
} else if (t1 && t2 && !t3) {
a12++;
} else if (t1 && !t2 && !t3) {
a1++;
} else if (!t1 && t2 && !t3) {
a2++;
} else if (t1 && !t2 && t3) {
a13++;
} else if (!t1 && t2 && t3) {
a23++;
} else if (!t1 && !t2 && t3) {
a3++;
}
}
}
let aTotal = a1 + a2 + a3 + a12 + a13 + a23 + a123;
// calc error
let error = d / maxD;
error += this._error(uvw, total, a123, aTotal);
error += this._error(uv, total, a12, aTotal);
error += this._error(uw, total, a13, aTotal);
error += this._error(vw, total, a23, aTotal);
error += this._error(u, total, a1, aTotal);
error += this._error(v, total, a2, aTotal);
error += this._error(w, total, a3, aTotal);
// better than before?
if (alpha === 0 || error < errorBest) {
alphaBest = alpha;
dBest = d;
rBest = r;
errorBest = error;
}
}
}
this.alphaBest = alphaBest;
this.dBest = dBest;
this.rBest = rBest;
this.errorBest = errorBest;
}
protected _error(u: number, total: number, a: number, aTotal: number): number {
// be brutal if basic error
if ((u === 0 && a !== 0) || (u !== 0 && a === 0)) {
return 1000;
}
return Math.abs(u / total - a / aTotal) * 100;
}
}