Vehicle energy consumption and production simulator
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

247 lines
7.2 KiB

namespace SvgDrawing {
export interface Rect {
x: number;
y: number;
width: number;
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 {
private invDataW: number = 0;
private invDataH: number = 0;
constructor(private data: Rect, private view: Rect) { this.update(); }
setData(r: Rect) { this.data = r; this.update(); }
setView(r: Rect) { this.view = r; this.update(); }
xDataToView(x: number) { return (x - this.data.x) / this.data.width * this.view.width + this.view.x; }
yDataToView(y: number) { return (y - this.data.y) / this.data.height * this.view.height + this.view.y; }
dataToView(p: Point, out_point: Point) {
out_point.x = (p.x - this.data.x) * this.invDataW * this.view.width + this.view.x;
out_point.y = (p.y - this.data.y) * this.invDataH * this.view.height + this.view.y;
}
private update() {
this.invDataW = 1.0 / this.data.width;
this.invDataH = 1.0 / this.data.height;
}
}
export interface LineStyle {
className: string;
}
export class SvgElement {
public viewport: Viewport;
public width: number;
public height: number;
constructor(private htmlElement: HTMLElement) {
let viewBox = htmlElement.getAttribute('viewBox').split(' ');
let r: Rect = { x: Number(viewBox[0]), y: Number(viewBox[1]), width: Number(viewBox[2]), height: Number(viewBox[3]) };
this.viewport = new Viewport(r, { x: r.x, y: r.y + r.height, width: r.width, height: -r.height });
this.width = r.width;
this.height = r.height;
}
clear() {
this.htmlElement.querySelectorAll('*').forEach(elt => elt.remove());
}
line(start: Point, end: Point): SVGLineElement {
let line = document.createElementNS('http://www.w3.org/2000/svg','line');
line.setAttribute('x1', this.viewport.xDataToView(start.x).toString());
line.setAttribute('y1', this.viewport.yDataToView(start.y).toString());
line.setAttribute('x2', this.viewport.xDataToView(end.x).toString());
line.setAttribute('y2', this.viewport.yDataToView(end.y).toString());
this.htmlElement.append(line);
return line;
}
text(position: Point, value: string, anchor?: string, verticalAlign?: number): SVGTextElement {
let text = document.createElementNS('http://www.w3.org/2000/svg','text');
text.setAttribute('x', this.viewport.xDataToView(position.x).toString());
text.setAttribute('y', this.viewport.yDataToView(position.y).toString());
if(anchor !== undefined) text.setAttribute('text-anchor', anchor);
if(verticalAlign !== undefined) text.setAttribute('dy', (1.0-verticalAlign) + 'em');
text.appendChild(document.createTextNode(value));
this.htmlElement.append(text);
return text;
}
graph(y: number[]): SVGPathElement[];
graph(x: number[], y: number[]): SVGPathElement[];
graph(y: number[], indices: number[], styles: LineStyle[]): SVGPathElement[];
graph(x: number[], y: number[], indices: number[], styles: LineStyle[]): SVGPathElement[];
graph(arg1: number[], arg2?: number[], arg3?: number[] | LineStyle[], arg4?: LineStyle[]): SVGPathElement[] {
let indices: number[] | null = null;
let styles: LineStyle[] | null = null;
if(arg3) {
if(arg4) {
indices = <number[]>arg3;
styles = arg4;
}
else {
indices = arg2;
styles = <LineStyle[]>arg3;
}
}
if(styles == null) {
styles = [{className: ''}];
}
let read = (idx: number, out_point: Point) => {
out_point.x = arg1[idx];
out_point.y = arg2[idx];
return true;
};
let reset = (idx: number) => {};
if(!arg2 || (arg3 && typeof(arg3[0]) !== 'number')) {
read = (idx: number, out_point: Point) => {
out_point.x = idx;
out_point.y = arg1[idx];
return true;
};
}
let num = arg1.length;
console.assert(!arg2 || num == arg2.length);
if(num <= 1) return null;
let optimizeCurveDist = 5;
let optimizeCurveAngle = 45.0;
if(optimizeCurveDist > 0 || optimizeCurveAngle < 0.0) {
let rawRead = read;
let dp = Math.cos(optimizeCurveAngle/180*Math.PI);
let lastDrawnPoint: Point = { x: 0, y: 0 };
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 };
let rawReset = reset;
reset = (idx: number) => {
rawReset(idx);
read(idx, lastDrawnPoint);
};
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;
}
}
let startTime = performance.now();
let count = 0;
let paths = [];
for(let styleIdx = 0; styleIdx < styles.length; ++styleIdx) {
let dataPoint: Point = { x: 0, y: 0 };
let viewPoint: Point = { x: 0, y: 0 };
let coordinates = '';
let open = false;
for(let idx = 0; idx < num; ++idx) {
let includePoint = !indices || indices[idx] == styleIdx;
if(!open && includePoint) {
reset(idx);
read(idx, dataPoint);
this.viewport.dataToView(dataPoint, viewPoint);
let space = coordinates == '' ? '' : ' ';
coordinates += space+'M'+Math.round(viewPoint.x)+','+Math.round(viewPoint.y) + ' L';
count += 1;
}
else if(open && (read(idx, dataPoint) || !includePoint)) {
this.viewport.dataToView(dataPoint, viewPoint);
coordinates += ' '+Math.round(viewPoint.x)+','+Math.round(viewPoint.y);
count += 1;
}
open = includePoint;
}
let style = styles[styleIdx];
let path = document.createElementNS('http://www.w3.org/2000/svg','path');
path.setAttribute('class', 'graph' + (style.className == '' ? '' : ' ' + style.className));
path.setAttribute('d', coordinates);
this.htmlElement.append(path);
paths.push(path);
}
let endTime = performance.now();
console.log("graph: " + count + " points, " + (endTime - startTime) + "ms");
return paths;
}
}
}