|
|
|
@ -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 optimizeCurveDist = 5; |
|
|
|
|
let optimizeCurveAngle = 45.0; |
|
|
|
|
if(optimizeCurveDist > 0 || optimizeCurveAngle < 0.0) { |
|
|
|
|
let rawRead = read; |
|
|
|
|
|
|
|
|
|
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 dp = Math.cos(optimizeCurveAngle/180*Math.PI); |
|
|
|
|
|
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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) { |
|
|
|
|
let isLast = (idx == num - 1); |
|
|
|
|
|
|
|
|
|
let newX = x ? x[idx] : idx; |
|
|
|
|
let newY = y[idx]; |
|
|
|
|
let dir = isLast ? 0 : (y[idx+1] > newY ? 1 : -1); |
|
|
|
|
|
|
|
|
|
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; |
|
|
|
|
if(read(idx, logicalPoint)) { |
|
|
|
|
this.viewport.logicalToView(logicalPoint, viewPoint); |
|
|
|
|
coordinates += Math.round(viewPoint.x)+','+Math.round(viewPoint.y)+' '; |
|
|
|
|
count += 1; |
|
|
|
|
} |
|
|
|
|
prevY = newY; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
console.log(count); |
|
|
|
|
|
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|