improved graph optimization
This commit is contained in:
parent
a4fbc4c36e
commit
059dbee24d
@ -74,7 +74,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
let resultsContainer = container.querySelector('.simulation-results');
|
||||
|
||||
let batteryChargeGraph = new SvgDrawing.SvgElement(resultsContainer.querySelector('.battery-charge-graph svg'));
|
||||
batteryChargeGraph.viewport.logical = { x: 0, y: 0, width: 365*24, height: parameters.batteryCapacity }
|
||||
batteryChargeGraph.viewport.setLogical({ x: 0, y: 0, width: 365*24, height: parameters.batteryCapacity });
|
||||
batteryChargeGraph.graph(simulationResult.batteryLevel);
|
||||
|
||||
resultsContainer.classList.toggle('is-hidden', false);
|
||||
|
@ -6,11 +6,59 @@ namespace SvgDrawing {
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface Point {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export class Point {
|
||||
public static add(a: Point, b: Point, out_result: Point) {
|
||||
out_result.x = a.x + b.x;
|
||||
out_result.y = a.y + b.y;
|
||||
}
|
||||
|
||||
public static subtract(a: Point, b: Point, out_result: Point) {
|
||||
out_result.x = a.x - b.x;
|
||||
out_result.y = a.y - b.y;
|
||||
}
|
||||
|
||||
public static normalize(v: Point) {
|
||||
let invN = 1.0 / Math.sqrt(v.x*v.x + v.y*v.y);
|
||||
v.x *= invN;
|
||||
v.y *= invN;
|
||||
}
|
||||
|
||||
public static dot(a: Point, b: Point) {
|
||||
return a.x*b.x + a.y*b.y;
|
||||
}
|
||||
|
||||
public static normalizedDot(a: Point, b: Point) {
|
||||
let NA = Math.sqrt(a.x*a.x + a.y*a.y);
|
||||
let NB = Math.sqrt(b.x*b.x + b.y*b.y);
|
||||
return (a.x*b.x + a.y*b.y) / NA / NB;
|
||||
}
|
||||
}
|
||||
|
||||
export class Viewport {
|
||||
constructor(public logical: Rect, public view: Rect) {}
|
||||
private invLogicalW: number = 0;
|
||||
private invLogicalH: number = 0;
|
||||
|
||||
constructor(private logical: Rect, private view: Rect) { this.update(); }
|
||||
|
||||
setLogical(r: Rect) { this.logical = r; this.update(); }
|
||||
|
||||
xLogicalToView(x: number) { return (x - this.logical.x) / this.logical.width * this.view.width + this.view.x; }
|
||||
yLogicalToView(y: number) { return (y - this.logical.y) / this.logical.height * this.view.height + this.view.y; }
|
||||
|
||||
logicalToView(p: Point, out_point: Point) {
|
||||
out_point.x = (p.x - this.logical.x) * this.invLogicalW * this.view.width + this.view.x;
|
||||
out_point.y = (p.y - this.logical.y) * this.invLogicalH * this.view.height + this.view.y;
|
||||
}
|
||||
|
||||
private update() {
|
||||
this.invLogicalW = 1.0 / this.logical.width;
|
||||
this.invLogicalH = 1.0 / this.logical.height;
|
||||
}
|
||||
}
|
||||
|
||||
export class SvgElement {
|
||||
@ -25,52 +73,97 @@ namespace SvgDrawing {
|
||||
graph(y: number[]): SVGPathElement;
|
||||
graph(x: number[], y: number[]): SVGPathElement;
|
||||
graph(arg1: number[], arg2?: number[]) {
|
||||
let x: number[] | null = arg1;
|
||||
let y: number[] = arg2;
|
||||
if(!y) {
|
||||
y = arg1;
|
||||
x = null;
|
||||
let read = (idx: number, out_point: Point) => {
|
||||
out_point.x = arg1[idx];
|
||||
out_point.y = arg2[idx];
|
||||
return true;
|
||||
};
|
||||
|
||||
if(!arg2) {
|
||||
read = (idx: number, out_point: Point) => {
|
||||
out_point.x = idx;
|
||||
out_point.y = arg1[idx];
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
let num = y.length;
|
||||
console.assert(!x || num == x.length);
|
||||
let num = arg1.length;
|
||||
console.assert(!arg2 || num == arg2.length);
|
||||
|
||||
if(num <= 1) return null;
|
||||
|
||||
let xStep = 6;
|
||||
|
||||
let coordinates = 'M'+Math.round(this.viewport.xLogicalToView(x? x[0] : 0))+','+Math.round(this.viewport.yLogicalToView(y[0]));
|
||||
coordinates += ' L';
|
||||
let lineStartX = x ? x[0] : 0;
|
||||
let prevX = lineStartX;
|
||||
let prevY = y[0];
|
||||
let yDir = y[1] > y[0] ? 1 : -1;
|
||||
|
||||
let count = 0;
|
||||
for(let idx = 0; idx < num; ++idx) {
|
||||
let isLast = (idx == num - 1);
|
||||
let optimizeCurveDist = 5;
|
||||
let optimizeCurveAngle = 45.0;
|
||||
if(optimizeCurveDist > 0 || optimizeCurveAngle < 0.0) {
|
||||
let rawRead = read;
|
||||
|
||||
let newX = x ? x[idx] : idx;
|
||||
let newY = y[idx];
|
||||
let dir = isLast ? 0 : (y[idx+1] > newY ? 1 : -1);
|
||||
let dp = Math.cos(optimizeCurveAngle/180*Math.PI);
|
||||
|
||||
if(newX >= lineStartX + xStep || dir != yDir || isLast) {
|
||||
coordinates += Math.round(this.viewport.xLogicalToView(newX))+','+Math.round(this.viewport.yLogicalToView(newY));
|
||||
if(!isLast) coordinates += ' ';
|
||||
lineStartX = newX;
|
||||
yDir = isLast ? 0 : (y[idx+1] > newY ? 1 : -1);
|
||||
++count;
|
||||
let lastDrawnPoint: Point = { x: 0, y: 0 };
|
||||
read(0, lastDrawnPoint);
|
||||
|
||||
let nextPoint: Point = { x: 0, y: 0 };
|
||||
let dir: Point = { x: 0, y: 0 };
|
||||
let nextDir: Point = { x: 0, y: 0 };
|
||||
let nextSegDir: Point = { x: 0, y: 0 };
|
||||
let perp: Point = { x: 0, y: 0 };
|
||||
|
||||
read = (idx: number, out_point: Point) => {
|
||||
rawRead(idx, out_point);
|
||||
if(idx == 0 || idx == num - 1) return true;
|
||||
|
||||
rawRead(idx + 1, nextPoint);
|
||||
Point.subtract(out_point, lastDrawnPoint, dir);
|
||||
Point.subtract(nextPoint, out_point, nextSegDir);
|
||||
|
||||
if(Point.normalizedDot(dir, nextSegDir) < dp) {
|
||||
lastDrawnPoint = { ...out_point };
|
||||
return true;
|
||||
}
|
||||
|
||||
Point.subtract(nextPoint, lastDrawnPoint, nextDir);
|
||||
|
||||
perp.x = -nextDir.y;
|
||||
perp.y = nextDir.x;
|
||||
Point.normalize(perp);
|
||||
|
||||
let d = Math.abs(Point.dot(perp, dir));
|
||||
if(d > optimizeCurveDist) {
|
||||
lastDrawnPoint = { ...out_point };
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
prevY = newY;
|
||||
}
|
||||
|
||||
console.log(count);
|
||||
let startTime = performance.now();
|
||||
let count = 0;
|
||||
|
||||
let logicalPoint: Point = { x: 0, y: 0 };
|
||||
let viewPoint: Point = { x: 0, y: 0 };
|
||||
read(0, logicalPoint);
|
||||
this.viewport.logicalToView(logicalPoint, viewPoint);
|
||||
|
||||
let coordinates = 'M'+Math.round(viewPoint.x)+','+Math.round(viewPoint.y);
|
||||
coordinates += ' L';
|
||||
|
||||
for(let idx = 0; idx < num; ++idx) {
|
||||
if(read(idx, logicalPoint)) {
|
||||
this.viewport.logicalToView(logicalPoint, viewPoint);
|
||||
coordinates += Math.round(viewPoint.x)+','+Math.round(viewPoint.y)+' ';
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let path = document.createElementNS('http://www.w3.org/2000/svg','path');
|
||||
path.setAttribute('class','graph');
|
||||
path.setAttribute('d', coordinates);
|
||||
this.htmlElement.append(path);
|
||||
|
||||
let endTime = performance.now();
|
||||
console.log("graph: " + count + " points, " + (endTime - startTime) + "ms");
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user