diff options
Diffstat (limited to 'src/client/northstar/utils')
| -rw-r--r-- | src/client/northstar/utils/ArrayUtil.ts | 90 | ||||
| -rw-r--r-- | src/client/northstar/utils/Extensions.ts | 29 | ||||
| -rw-r--r-- | src/client/northstar/utils/GeometryUtil.ts | 129 | ||||
| -rw-r--r-- | src/client/northstar/utils/IDisposable.ts | 3 | ||||
| -rw-r--r-- | src/client/northstar/utils/IEquatable.ts | 3 | ||||
| -rw-r--r-- | src/client/northstar/utils/KeyCodes.ts | 137 | ||||
| -rw-r--r-- | src/client/northstar/utils/LABColor.ts | 90 | ||||
| -rw-r--r-- | src/client/northstar/utils/MathUtil.ts | 241 | ||||
| -rw-r--r-- | src/client/northstar/utils/PartialClass.ts | 7 | ||||
| -rw-r--r-- | src/client/northstar/utils/SizeConverter.ts | 99 | ||||
| -rw-r--r-- | src/client/northstar/utils/StyleContants.ts | 95 | ||||
| -rw-r--r-- | src/client/northstar/utils/Utils.ts | 75 |
12 files changed, 998 insertions, 0 deletions
diff --git a/src/client/northstar/utils/ArrayUtil.ts b/src/client/northstar/utils/ArrayUtil.ts new file mode 100644 index 000000000..f35c98317 --- /dev/null +++ b/src/client/northstar/utils/ArrayUtil.ts @@ -0,0 +1,90 @@ +import { Exception } from "../model/idea/idea"; + +export class ArrayUtil { + + public static Contains(arr1: any[], arr2: any): boolean { + if (arr1.length === 0) { + return false; + } + let isComplex = typeof arr1[0] === "object"; + for (let i = 0; i < arr1.length; i++) { + if (isComplex && "Equals" in arr1[i]) { + if (arr1[i].Equals(arr2)) { + return true; + } + } + else { + if (arr1[i] === arr2) { + return true; + } + } + } + return false; + } + + + public static RemoveMany(arr: any[], elements: Object[]) { + elements.forEach(e => ArrayUtil.Remove(arr, e)); + } + + public static AddMany(arr: any[], others: Object[]) { + arr.push(...others); + } + + public static Clear(arr: any[]) { + arr.splice(0, arr.length); + } + + + public static Remove(arr: any[], other: Object) { + const index = ArrayUtil.IndexOfWithEqual(arr, other); + if (index === -1) { + return; + } + arr.splice(index, 1); + } + + + public static First<T>(arr: T[], predicate: (x: any) => boolean): T { + let filtered = arr.filter(predicate); + if (filtered.length > 0) { + return filtered[0]; + } + throw new Exception() + } + + public static FirstOrDefault<T>(arr: T[], predicate: (x: any) => boolean): T | undefined { + let filtered = arr.filter(predicate); + if (filtered.length > 0) { + return filtered[0]; + } + return undefined; + } + + public static Distinct(arr: any[]): any[] { + let ret = []; + for (let i = 0; i < arr.length; i++) { + if (!ArrayUtil.Contains(ret, arr[i])) { + ret.push(arr[i]); + } + } + return ret; + } + + public static IndexOfWithEqual(arr: any[], other: any): number { + for (let i = 0; i < arr.length; i++) { + let isComplex = typeof arr[0] === "object"; + if (isComplex && "Equals" in arr[i]) { + if (arr[i].Equals(other)) { + return i; + } + } + else { + if (arr[i] === other) { + return i; + } + } + } + return -1; + } +}
\ No newline at end of file diff --git a/src/client/northstar/utils/Extensions.ts b/src/client/northstar/utils/Extensions.ts new file mode 100644 index 000000000..7c2b7fc9d --- /dev/null +++ b/src/client/northstar/utils/Extensions.ts @@ -0,0 +1,29 @@ +interface String { + ReplaceAll(toReplace: string, replacement: string): string; + Truncate(length: number, replacement: string): String; +} + +String.prototype.ReplaceAll = function (toReplace: string, replacement: string): string { + var target = this; + return target.split(toReplace).join(replacement); +} + +String.prototype.Truncate = function (length: number, replacement: string): String { + var target = this; + if (target.length >= length) { + target = target.slice(0, Math.max(0, length - replacement.length)) + replacement; + } + return target; +} + +interface Math { + log10(val: number): number; +} + +Math.log10 = function (val: number): number { + return Math.log(val) / Math.LN10; +} + +declare interface ObjectConstructor { + assign(...objects: Object[]): Object; +} diff --git a/src/client/northstar/utils/GeometryUtil.ts b/src/client/northstar/utils/GeometryUtil.ts new file mode 100644 index 000000000..d5f3ba631 --- /dev/null +++ b/src/client/northstar/utils/GeometryUtil.ts @@ -0,0 +1,129 @@ +import { MathUtil, PIXIRectangle, PIXIPoint } from "./MathUtil"; + + +export class GeometryUtil { + + public static ComputeBoundingBox(points: { x: number, y: number }[], scale = 1, padding: number = 0): PIXIRectangle { + let minX: number = Number.MAX_VALUE; + let minY: number = Number.MAX_VALUE; + let maxX: number = Number.MIN_VALUE; + let maxY: number = Number.MIN_VALUE; + for (var i = 0; i < points.length; i++) { + if (points[i].x < minX) + minX = points[i].x; + if (points[i].y < minY) + minY = points[i].y; + if (points[i].x > maxX) + maxX = points[i].x; + if (points[i].y > maxY) + maxY = points[i].y; + } + return new PIXIRectangle(minX * scale - padding, minY * scale - padding, (maxX - minX) * scale + padding * 2, (maxY - minY) * scale + padding * 2); + } + + public static RectangleOverlap(rect1: PIXIRectangle, rect2: PIXIRectangle) { + let x_overlap = Math.max(0, Math.min(rect1.right, rect2.right) - Math.max(rect1.left, rect2.left)); + let y_overlap = Math.max(0, Math.min(rect1.bottom, rect2.bottom) - Math.max(rect1.top, rect2.top)); + return x_overlap * y_overlap; + } + + public static RotatePoints(center: { x: number, y: number }, points: { x: number, y: number }[], angle: number): PIXIPoint[] { + const rotate = (cx: number, cy: number, x: number, y: number, angle: number) => { + const radians = angle, + cos = Math.cos(radians), + sin = Math.sin(radians), + nx = (cos * (x - cx)) + (sin * (y - cy)) + cx, + ny = (cos * (y - cy)) - (sin * (x - cx)) + cy; + return new PIXIPoint(nx, ny); + } + return points.map(p => rotate(center.x, center.y, p.x, p.y, angle)); + } + + public static LineByLeastSquares(points: { x: number, y: number }[]): PIXIPoint[] { + let sum_x: number = 0; + let sum_y: number = 0; + let sum_xy: number = 0; + let sum_xx: number = 0; + let count: number = 0; + + let x: number = 0; + let y: number = 0; + + + if (points.length === 0) { + return []; + } + + for (let v = 0; v < points.length; v++) { + x = points[v].x; + y = points[v].y; + sum_x += x; + sum_y += y; + sum_xx += x * x; + sum_xy += x * y; + count++; + } + + let m = (count * sum_xy - sum_x * sum_y) / (count * sum_xx - sum_x * sum_x); + let b = (sum_y / count) - (m * sum_x) / count; + let result: PIXIPoint[] = new Array<PIXIPoint>(); + + for (let v = 0; v < points.length; v++) { + x = points[v].x; + y = x * m + b; + result.push(new PIXIPoint(x, y)); + } + return result; + } + + // public static PointInsidePolygon(vs:Point[], x:number, y:number):boolean { + // // ray-casting algorithm based on + // // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html + + // var inside = false; + // for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) { + // var xi = vs[i].x, yi = vs[i].y; + // var xj = vs[j].x, yj = vs[j].y; + + // var intersect = ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + // if (intersect) + // inside = !inside; + // } + + // return inside; + // }; + + public static IntersectLines(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number): boolean { + let a1: number, a2: number, b1: number, b2: number, c1: number, c2: number; + let r1: number, r2: number, r3: number, r4: number; + let denom: number, offset: number, num: number; + + a1 = y2 - y1; + b1 = x1 - x2; + c1 = (x2 * y1) - (x1 * y2); + r3 = ((a1 * x3) + (b1 * y3) + c1); + r4 = ((a1 * x4) + (b1 * y4) + c1); + + if ((r3 !== 0) && (r4 !== 0) && (MathUtil.Sign(r3) === MathUtil.Sign(r4))) { + return false; + } + + a2 = y4 - y3; + b2 = x3 - x4; + c2 = (x4 * y3) - (x3 * y4); + + r1 = (a2 * x1) + (b2 * y1) + c2; + r2 = (a2 * x2) + (b2 * y2) + c2; + + if ((r1 !== 0) && (r2 !== 0) && (MathUtil.Sign(r1) === MathUtil.Sign(r2))) { + return false; + } + + denom = (a1 * b2) - (a2 * b1); + + if (denom === 0) { + return false; + } + return true; + } +}
\ No newline at end of file diff --git a/src/client/northstar/utils/IDisposable.ts b/src/client/northstar/utils/IDisposable.ts new file mode 100644 index 000000000..5e9843326 --- /dev/null +++ b/src/client/northstar/utils/IDisposable.ts @@ -0,0 +1,3 @@ +export interface IDisposable { + Dispose(): void; +}
\ No newline at end of file diff --git a/src/client/northstar/utils/IEquatable.ts b/src/client/northstar/utils/IEquatable.ts new file mode 100644 index 000000000..2f81c2478 --- /dev/null +++ b/src/client/northstar/utils/IEquatable.ts @@ -0,0 +1,3 @@ +export interface IEquatable { + Equals(other: Object): boolean; +}
\ No newline at end of file diff --git a/src/client/northstar/utils/KeyCodes.ts b/src/client/northstar/utils/KeyCodes.ts new file mode 100644 index 000000000..044569ffe --- /dev/null +++ b/src/client/northstar/utils/KeyCodes.ts @@ -0,0 +1,137 @@ +/** + * Class contains the keycodes for keys on your keyboard. + * + * Useful for auto completion: + * + * ``` + * switch (event.key) + * { + * case KeyCode.UP: + * { + * // Up key pressed + * break; + * } + * case KeyCode.DOWN: + * { + * // Down key pressed + * break; + * } + * case KeyCode.LEFT: + * { + * // Left key pressed + * break; + * } + * case KeyCode.RIGHT: + * { + * // Right key pressed + * break; + * } + * default: + * { + * // ignore + * break; + * } + * } + * ``` + */ +export class KeyCodes +{ + public static TAB:number = 9; + public static CAPS_LOCK:number = 20; + public static SHIFT:number = 16; + public static CONTROL:number = 17; + public static SPACE:number = 32; + public static DOWN:number = 40; + public static UP:number = 38; + public static LEFT:number = 37; + public static RIGHT:number = 39; + public static ESCAPE:number = 27; + public static F1:number = 112; + public static F2:number = 113; + public static F3:number = 114; + public static F4:number = 115; + public static F5:number = 116; + public static F6:number = 117; + public static F7:number = 118; + public static F8:number = 119; + public static F9:number = 120; + public static F10:number = 121; + public static F11:number = 122; + public static F12:number = 123; + public static INSERT:number = 45; + public static HOME:number = 36; + public static PAGE_UP:number = 33; + public static PAGE_DOWN:number = 34; + public static DELETE:number = 46; + public static END:number = 35; + public static ENTER:number = 13; + public static BACKSPACE:number = 8; + public static NUMPAD_0:number = 96; + public static NUMPAD_1:number = 97; + public static NUMPAD_2:number = 98; + public static NUMPAD_3:number = 99; + public static NUMPAD_4:number = 100; + public static NUMPAD_5:number = 101; + public static NUMPAD_6:number = 102; + public static NUMPAD_7:number = 103; + public static NUMPAD_8:number = 104; + public static NUMPAD_9:number = 105; + public static NUMPAD_DIVIDE:number = 111; + public static NUMPAD_ADD:number = 107; + public static NUMPAD_ENTER:number = 13; + public static NUMPAD_DECIMAL:number = 110; + public static NUMPAD_SUBTRACT:number = 109; + public static NUMPAD_MULTIPLY:number = 106; + public static SEMICOLON:number = 186; + public static EQUAL:number = 187; + public static COMMA:number = 188; + public static MINUS:number = 189; + public static PERIOD:number = 190; + public static SLASH:number = 191; + public static BACKQUOTE:number = 192; + public static LEFTBRACKET:number = 219; + public static BACKSLASH:number = 220; + public static RIGHTBRACKET:number = 221; + public static QUOTE:number = 222; + public static ALT:number = 18; + public static COMMAND:number = 15; + public static NUMPAD:number = 21; + public static A:number = 65; + public static B:number = 66; + public static C:number = 67; + public static D:number = 68; + public static E:number = 69; + public static F:number = 70; + public static G:number = 71; + public static H:number = 72; + public static I:number = 73; + public static J:number = 74; + public static K:number = 75; + public static L:number = 76; + public static M:number = 77; + public static N:number = 78; + public static O:number = 79; + public static P:number = 80; + public static Q:number = 81; + public static R:number = 82; + public static S:number = 83; + public static T:number = 84; + public static U:number = 85; + public static V:number = 86; + public static W:number = 87; + public static X:number = 88; + public static Y:number = 89; + public static Z:number = 90; + public static NUM_0:number = 48; + public static NUM_1:number = 49; + public static NUM_2:number = 50; + public static NUM_3:number = 51; + public static NUM_4:number = 52; + public static NUM_5:number = 53; + public static NUM_6:number = 54; + public static NUM_7:number = 55; + public static NUM_8:number = 56; + public static NUM_9:number = 57; + public static SUBSTRACT:number = 189; + public static ADD:number = 187; +}
\ No newline at end of file diff --git a/src/client/northstar/utils/LABColor.ts b/src/client/northstar/utils/LABColor.ts new file mode 100644 index 000000000..72e46fb7f --- /dev/null +++ b/src/client/northstar/utils/LABColor.ts @@ -0,0 +1,90 @@ + +export class LABColor { + public L: number; + public A: number; + public B: number; + + // constructor - takes three floats for lightness and color-opponent dimensions + constructor(l: number, a: number, b: number) { + this.L = l; + this.A = a; + this.B = b; + } + + // static function for linear interpolation between two LABColors + public static Lerp(a: LABColor, b: LABColor, t: number): LABColor { + return new LABColor(LABColor.LerpNumber(a.L, b.L, t), LABColor.LerpNumber(a.A, b.A, t), LABColor.LerpNumber(a.B, b.B, t)); + } + + public static LerpNumber(a: number, b: number, percent: number): number { + return a + percent * (b - a); + } + + static hexToRGB(hex: number, alpha: number): number[] { + var r = (hex & (0xff << 16)) >> 16; + var g = (hex & (0xff << 8)) >> 8; + var b = (hex & (0xff << 0)) >> 0; + return [r, g, b]; + } + static RGBtoHex(red: number, green: number, blue: number): number { + return blue | (green << 8) | (red << 16); + } + + public static RGBtoHexString(rgb: number): string { + let str = "#" + this.hex((rgb & (0xff << 16)) >> 16) + this.hex((rgb & (0xff << 8)) >> 8) + this.hex((rgb & (0xff << 0)) >> 0); + return str; + } + + static hex(x: number): string { + var hexDigits = new Array + ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"); + return isNaN(x) ? "00" : hexDigits[(x - x % 16) / 16] + hexDigits[x % 16]; + } + + public static FromColor(c: number): LABColor { + var rgb = LABColor.hexToRGB(c, 0); + var r = LABColor.d3_rgb_xyz(rgb[0] * 255); + var g = LABColor.d3_rgb_xyz(rgb[1] * 255); + var b = LABColor.d3_rgb_xyz(rgb[2] * 255); + + var x = LABColor.d3_xyz_lab((0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / LABColor.d3_lab_X); + var y = LABColor.d3_xyz_lab((0.2126729 * r + 0.7151522 * g + 0.0721750 * b) / LABColor.d3_lab_Y); + var z = LABColor.d3_xyz_lab((0.0193339 * r + 0.1191920 * g + 0.9503041 * b) / LABColor.d3_lab_Z); + var lab = new LABColor(116 * y - 16, 500 * (x - y), 200 * (y - z)); + return lab; + } + + private static d3_lab_X: number = 0.950470; + private static d3_lab_Y: number = 1; + private static d3_lab_Z: number = 1.088830; + + public static d3_lab_xyz(x: number): number { + return x > 0.206893034 ? x * x * x : (x - 4 / 29) / 7.787037; + } + + public static d3_xyz_rgb(r: number): number { + return Math.round(255 * (r <= 0.00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - 0.055)); + } + + public static d3_rgb_xyz(r: number): number { + return (r /= 255) <= 0.04045 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4); + } + + public static d3_xyz_lab(x: number): number { + return x > 0.008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29; + } + + public static ToColor(lab: LABColor): number { + var y = (lab.L + 16) / 116; + var x = y + lab.A / 500; + var z = y - lab.B / 200; + x = LABColor.d3_lab_xyz(x) * LABColor.d3_lab_X; + y = LABColor.d3_lab_xyz(y) * LABColor.d3_lab_Y; + z = LABColor.d3_lab_xyz(z) * LABColor.d3_lab_Z; + + return LABColor.RGBtoHex( + LABColor.d3_xyz_rgb(3.2404542 * x - 1.5371385 * y - 0.4985314 * z) / 255, + LABColor.d3_xyz_rgb(-0.9692660 * x + 1.8760108 * y + 0.0415560 * z) / 255, + LABColor.d3_xyz_rgb(0.0556434 * x - 0.2040259 * y + 1.0572252 * z) / 255); + } +}
\ No newline at end of file diff --git a/src/client/northstar/utils/MathUtil.ts b/src/client/northstar/utils/MathUtil.ts new file mode 100644 index 000000000..bb7e73871 --- /dev/null +++ b/src/client/northstar/utils/MathUtil.ts @@ -0,0 +1,241 @@ + + +export class PIXIPoint { + public get x() { return this.coords[0]; } + public get y() { return this.coords[1]; } + public set x(value: number) { this.coords[0] = value; } + public set y(value: number) { this.coords[1] = value; } + public coords: number[] = [0, 0]; + constructor(x: number, y: number) { + this.coords[0] = x; + this.coords[1] = y; + } +} + +export class PIXIRectangle { + public x: number; + public y: number; + public width: number; + public height: number + public get left() { return this.x } + public get right() { return this.x + this.width; } + public get top() { return this.y } + public get bottom() { return this.top + this.height } + public static get EMPTY() { return new PIXIRectangle(0, 0, -1, -1); } + constructor(x: number, y: number, width: number, height: number) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } +} + +export class MathUtil { + + public static EPSILON: number = 0.001; + + public static Sign(value: number): number { + return value >= 0 ? 1 : -1; + } + + public static AddPoint(p1: PIXIPoint, p2: PIXIPoint, inline: boolean = false): PIXIPoint { + if (inline) { + p1.x += p2.x; + p1.y += p2.y; + return p1; + } + else { + return new PIXIPoint(p1.x + p2.x, p1.y + p2.y); + } + } + + public static Perp(p1: PIXIPoint): PIXIPoint { + return new PIXIPoint(-p1.y, p1.x); + } + + public static DividePoint(p1: PIXIPoint, by: number, inline: boolean = false): PIXIPoint { + if (inline) { + p1.x /= by; + p1.y /= by; + return p1; + } + else { + return new PIXIPoint(p1.x / by, p1.y / by); + } + } + + public static MultiplyConstant(p1: PIXIPoint, by: number, inline: boolean = false) { + if (inline) { + p1.x *= by; + p1.y *= by; + return p1; + } + else { + return new PIXIPoint(p1.x * by, p1.y * by); + } + } + + public static SubtractPoint(p1: PIXIPoint, p2: PIXIPoint, inline: boolean = false): PIXIPoint { + if (inline) { + p1.x -= p2.x; + p1.y -= p2.y; + return p1; + } + else { + return new PIXIPoint(p1.x - p2.x, p1.y - p2.y); + } + } + + public static Area(rect: PIXIRectangle): number { + return rect.width * rect.height; + } + + public static DistToLineSegment(v: PIXIPoint, w: PIXIPoint, p: PIXIPoint) { + // Return minimum distance between line segment vw and point p + var l2 = MathUtil.DistSquared(v, w); // i.e. |w-v|^2 - avoid a sqrt + if (l2 == 0.0) return MathUtil.Dist(p, v); // v == w case + // Consider the line extending the segment, parameterized as v + t (w - v). + // We find projection of point p onto the line. + // It falls where t = [(p-v) . (w-v)] / |w-v|^2 + // We clamp t from [0,1] to handle points outside the segment vw. + var dot = MathUtil.Dot( + MathUtil.SubtractPoint(p, v), + MathUtil.SubtractPoint(w, v)) / l2; + var t = Math.max(0, Math.min(1, dot)); + // Projection falls on the segment + var projection = MathUtil.AddPoint(v, + MathUtil.MultiplyConstant( + MathUtil.SubtractPoint(w, v), t)); + return MathUtil.Dist(p, projection); + } + + public static LineSegmentIntersection(ps1: PIXIPoint, pe1: PIXIPoint, ps2: PIXIPoint, pe2: PIXIPoint): PIXIPoint | undefined { + var a1 = pe1.y - ps1.y; + var b1 = ps1.x - pe1.x; + + var a2 = pe2.y - ps2.y; + var b2 = ps2.x - pe2.x; + + var delta = a1 * b2 - a2 * b1; + if (delta == 0) { + return undefined; + } + var c2 = a2 * ps2.x + b2 * ps2.y; + var c1 = a1 * ps1.x + b1 * ps1.y; + var invdelta = 1 / delta; + return new PIXIPoint((b2 * c1 - b1 * c2) * invdelta, (a1 * c2 - a2 * c1) * invdelta); + } + + public static PointInPIXIRectangle(p: PIXIPoint, rect: PIXIRectangle): boolean { + if (p.x < rect.left - this.EPSILON) + return false; + if (p.x > rect.right + this.EPSILON) + return false; + if (p.y < rect.top - this.EPSILON) + return false; + if (p.y > rect.bottom + this.EPSILON) + return false; + + return true; + } + + public static LinePIXIRectangleIntersection(lineFrom: PIXIPoint, lineTo: PIXIPoint, rect: PIXIRectangle): Array<PIXIPoint> { + var r1 = new PIXIPoint(rect.left, rect.top); + var r2 = new PIXIPoint(rect.right, rect.top); + var r3 = new PIXIPoint(rect.right, rect.bottom); + var r4 = new PIXIPoint(rect.left, rect.bottom); + var ret = new Array<PIXIPoint>(); + var dist = this.Dist(lineFrom, lineTo) + var inter = this.LineSegmentIntersection(lineFrom, lineTo, r1, r2); + if (inter != null && this.PointInPIXIRectangle(inter, rect) && + this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) + ret.push(inter); + inter = this.LineSegmentIntersection(lineFrom, lineTo, r2, r3); + if (inter != null && this.PointInPIXIRectangle(inter, rect) && + this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) + ret.push(inter); + inter = this.LineSegmentIntersection(lineFrom, lineTo, r3, r4); + if (inter != null && this.PointInPIXIRectangle(inter, rect) && + this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) + ret.push(inter); + inter = this.LineSegmentIntersection(lineFrom, lineTo, r4, r1); + if (inter != null && this.PointInPIXIRectangle(inter, rect) && + this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) + ret.push(inter); + return ret; + } + + public static Intersection(rect1: PIXIRectangle, rect2: PIXIRectangle): PIXIRectangle { + const left = Math.max(rect1.x, rect2.x); + const right = Math.min(rect1.x + rect1.width, rect2.x + rect2.width); + const top = Math.max(rect1.y, rect2.y); + const bottom = Math.min(rect1.y + rect1.height, rect2.y + rect2.height); + return new PIXIRectangle(left, top, right - left, bottom - top); + } + + public static Dist(p1: PIXIPoint, p2: PIXIPoint): number { + return Math.sqrt(MathUtil.DistSquared(p1, p2)); + } + + public static Dot(p1: PIXIPoint, p2: PIXIPoint): number { + return p1.x * p2.x + p1.y * p2.y + } + + public static Normalize(p1: PIXIPoint) { + var d = this.Length(p1); + return new PIXIPoint(p1.x / d, p1.y / d); + } + + public static Length(p1: PIXIPoint): number { + return Math.sqrt(p1.x * p1.x + p1.y * p1.y); + } + + public static DistSquared(p1: PIXIPoint, p2: PIXIPoint): number { + const a = p1.x - p2.x; + const b = p1.y - p2.y; + return (a * a + b * b) + } + + public static RectIntersectsRect(r1: PIXIRectangle, r2: PIXIRectangle): boolean { + return !(r2.x > r1.x + r1.width || + r2.x + r2.width < r1.x || + r2.y > r1.y + r1.height || + r2.y + r2.height < r1.y); + } + + public static ArgMin(temp: number[]): number { + let index = 0; + let value = temp[0]; + for (let i = 1; i < temp.length; i++) { + if (temp[i] < value) { + value = temp[i]; + index = i; + } + } + return index; + } + + public static ArgMax(temp: number[]): number { + let index = 0; + let value = temp[0]; + for (let i = 1; i < temp.length; i++) { + if (temp[i] > value) { + value = temp[i]; + index = i; + } + } + return index; + } + + public static Combinations<T>(chars: T[]) { + let result = new Array<T>(); + let f = (prefix: any, chars: any) => { + for (let i = 0; i < chars.length; i++) { + result.push(prefix.concat(chars[i])); + f(prefix.concat(chars[i]), chars.slice(i + 1)); + } + }; + f([], chars); + return result; + } +}
\ No newline at end of file diff --git a/src/client/northstar/utils/PartialClass.ts b/src/client/northstar/utils/PartialClass.ts new file mode 100644 index 000000000..2f20de96f --- /dev/null +++ b/src/client/northstar/utils/PartialClass.ts @@ -0,0 +1,7 @@ + +export class PartialClass<T> { + + constructor(data?: Partial<T>) { + Object.assign(this, data); + } +}
\ No newline at end of file diff --git a/src/client/northstar/utils/SizeConverter.ts b/src/client/northstar/utils/SizeConverter.ts new file mode 100644 index 000000000..bb91ed4a7 --- /dev/null +++ b/src/client/northstar/utils/SizeConverter.ts @@ -0,0 +1,99 @@ +import { PIXIPoint } from "./MathUtil"; +import { VisualBinRange } from "../model/binRanges/VisualBinRange"; +import { Bin, DoubleValueAggregateResult, AggregateKey } from "../model/idea/idea"; +import { ModelHelpers } from "../model/ModelHelpers"; +import { observable, action, computed } from "mobx"; + +export class SizeConverter { + public DataMins: Array<number> = new Array<number>(2); + public DataMaxs: Array<number> = new Array<number>(2); + public DataRanges: Array<number> = new Array<number>(2); + public MaxLabelSizes: Array<PIXIPoint> = new Array<PIXIPoint>(2); + public RenderDimension: number = 300; + + @observable _leftOffset: number = 40; + @observable _rightOffset: number = 20; + @observable _topOffset: number = 20; + @observable _bottomOffset: number = 45; + @observable _labelAngle: number = 0; + @observable _isSmall: boolean = false; + @observable public Initialized = 0; + + @action public SetIsSmall(isSmall: boolean) { this._isSmall = isSmall; } + @action public SetLabelAngle(angle: number) { this._labelAngle = angle; } + @computed public get IsSmall() { return this._isSmall; } + @computed public get LabelAngle() { return this._labelAngle; } + @computed public get LeftOffset() { return this.IsSmall ? 5 : this._leftOffset; } + @computed public get RightOffset() { return this.IsSmall ? 5 : !this._labelAngle ? this._bottomOffset : Math.max(this._rightOffset, Math.cos(this._labelAngle) * (this.MaxLabelSizes[0].x + 18)); } + @computed public get TopOffset() { return this.IsSmall ? 5 : this._topOffset; } + @computed public get BottomOffset() { return this.IsSmall ? 25 : !this._labelAngle ? this._bottomOffset : Math.max(this._bottomOffset, Math.sin(this._labelAngle) * (this.MaxLabelSizes[0].x + 18)) + 18; } + + public SetVisualBinRanges(visualBinRanges: Array<VisualBinRange>) { + this.Initialized++; + var xLabels = visualBinRanges[0].GetLabels(); + var yLabels = visualBinRanges[1].GetLabels(); + var xLabelStrings = xLabels.map(l => l.label!).sort(function (a, b) { return b.length - a.length }); + var yLabelStrings = yLabels.map(l => l.label!).sort(function (a, b) { return b.length - a.length }); + + var metricsX = { width: 75 }; // RenderUtils.MeasureText(FontStyles.Default.fontFamily.toString(), 12, // FontStyles.AxisLabel.fontSize as number, + //xLabelStrings[0]!.slice(0, 20)) // StyleConstants.MAX_CHAR_FOR_HISTOGRAM_LABELS)); + var metricsY = { width: 22 }; // RenderUtils.MeasureText(FontStyles.Default.fontFamily.toString(), 12, // FontStyles.AxisLabel.fontSize as number, + // yLabelStrings[0]!.slice(0, 20)); // StyleConstants.MAX_CHAR_FOR_HISTOGRAM_LABELS)); + this.MaxLabelSizes[0] = new PIXIPoint(metricsX.width, 12);// FontStyles.AxisLabel.fontSize as number); + this.MaxLabelSizes[1] = new PIXIPoint(metricsY.width, 12); // FontStyles.AxisLabel.fontSize as number); + + this._leftOffset = Math.max(10, metricsY.width + 10 + 20); + + this.DataMins[0] = xLabels.map(l => l.minValue!).reduce((m, c) => Math.min(m, c), Number.MAX_VALUE); + this.DataMins[1] = yLabels.map(l => l.minValue!).reduce((m, c) => Math.min(m, c), Number.MAX_VALUE); + this.DataMaxs[0] = xLabels.map(l => l.maxValue!).reduce((m, c) => Math.max(m, c), Number.MIN_VALUE); + this.DataMaxs[1] = yLabels.map(l => l.maxValue!).reduce((m, c) => Math.max(m, c), Number.MIN_VALUE); + + this.DataRanges[0] = this.DataMaxs[0] - this.DataMins[0]; + this.DataRanges[1] = this.DataMaxs[1] - this.DataMins[1]; + } + + public DataToScreenNormalizedRange(dataValue: number, normalization: number, axis: number, binBrushMaxAxis: number) { + var value = normalization != 1 - axis || binBrushMaxAxis == 0 ? dataValue : (dataValue - 0) / (binBrushMaxAxis - 0) * this.DataRanges[axis]; + var from = this.DataToScreenCoord(Math.min(0, value), axis); + var to = this.DataToScreenCoord(Math.max(0, value), axis); + return [from, value, to]; + } + + public DataToScreenPointRange(axis: number, bin: Bin, aggregateKey: AggregateKey) { + var value = ModelHelpers.GetAggregateResult(bin, aggregateKey) as DoubleValueAggregateResult; + if (value && value.hasResult) + return [this.DataToScreenCoord(value.result!, axis) - 5, + this.DataToScreenCoord(value.result!, axis) + 5]; + return [undefined, undefined]; + } + + public DataToScreenXAxisRange(visualBinRanges: VisualBinRange[], index: number, bin: Bin) { + var value = visualBinRanges[0].GetValueFromIndex(bin.binIndex!.indices![index]); + return [this.DataToScreenX(value), this.DataToScreenX(visualBinRanges[index].AddStep(value))] + } + public DataToScreenYAxisRange(visualBinRanges: VisualBinRange[], index: number, bin: Bin) { + var value = visualBinRanges[1].GetValueFromIndex(bin.binIndex!.indices![index]); + return [this.DataToScreenY(value), this.DataToScreenY(visualBinRanges[index].AddStep(value))] + } + + public DataToScreenX(x: number): number { + return ((x - this.DataMins[0]) / this.DataRanges[0]) * this.RenderDimension; + } + public DataToScreenY(y: number, flip: boolean = true) { + var retY = ((y - this.DataMins[1]) / this.DataRanges[1]) * this.RenderDimension; + return flip ? (this.RenderDimension) - retY : retY; + } + public DataToScreenCoord(v: number, axis: number) { + if (axis == 0) + return this.DataToScreenX(v); + return this.DataToScreenY(v); + } + public DataToScreenRange(minVal: number, maxVal: number, axis: number) { + let xFrom = this.DataToScreenX(axis === 0 ? minVal : this.DataMins[0]); + let xTo = this.DataToScreenX(axis === 0 ? maxVal : this.DataMaxs[0]); + let yFrom = this.DataToScreenY(axis === 1 ? minVal : this.DataMins[1]); + let yTo = this.DataToScreenY(axis === 1 ? maxVal : this.DataMaxs[1]); + return { xFrom, yFrom, xTo, yTo } + } +}
\ No newline at end of file diff --git a/src/client/northstar/utils/StyleContants.ts b/src/client/northstar/utils/StyleContants.ts new file mode 100644 index 000000000..ac8617e3b --- /dev/null +++ b/src/client/northstar/utils/StyleContants.ts @@ -0,0 +1,95 @@ +import { PIXIPoint } from "./MathUtil"; + +export class StyleConstants { + + static DEFAULT_FONT: string = "Roboto Condensed"; + + static MENU_SUBMENU_WIDTH: number = 85; + static MENU_SUBMENU_HEIGHT: number = 400; + static MENU_BOX_SIZE: PIXIPoint = new PIXIPoint(80, 35); + static MENU_BOX_PADDING: number = 10; + + static OPERATOR_MENU_LARGE: number = 35; + static OPERATOR_MENU_SMALL: number = 25; + static BRUSH_PALETTE: number[] = [0x42b43c, 0xfa217f, 0x6a9c75, 0xfb5de7, 0x25b8ea, 0x9b5bc4, 0xda9f63, 0xe23209, 0xfb899b, 0x94a6fd] + static GAP: number = 3; + + static BACKGROUND_COLOR: number = 0xF3F3F3; + static TOOL_TIP_BACKGROUND_COLOR: number = 0xffffff; + static LIGHT_TEXT_COLOR: number = 0xffffff; + static LIGHT_TEXT_COLOR_STR: string = StyleConstants.HexToHexString(StyleConstants.LIGHT_TEXT_COLOR); + static DARK_TEXT_COLOR: number = 0x282828; + static HIGHLIGHT_TEXT_COLOR: number = 0xffcc00; + static FPS_TEXT_COLOR: number = StyleConstants.DARK_TEXT_COLOR; + static CORRELATION_LABEL_TEXT_COLOR_STR: string = StyleConstants.HexToHexString(StyleConstants.DARK_TEXT_COLOR); + static LOADING_SCREEN_TEXT_COLOR_STR: string = StyleConstants.HexToHexString(StyleConstants.DARK_TEXT_COLOR); + static ERROR_COLOR: number = 0x540E25; + static WARNING_COLOR: number = 0xE58F24; + static LOWER_THAN_NAIVE_COLOR: number = 0xee0000; + static HIGHLIGHT_COLOR: number = 0x82A8D9; + static HIGHLIGHT_COLOR_STR: string = StyleConstants.HexToHexString(StyleConstants.HIGHLIGHT_COLOR); + static OPERATOR_BACKGROUND_COLOR: number = 0x282828; + static LOADING_ANIMATION_COLOR: number = StyleConstants.OPERATOR_BACKGROUND_COLOR; + static MENU_COLOR: number = 0x282828; + static MENU_FONT_COLOR: number = StyleConstants.LIGHT_TEXT_COLOR; + static MENU_SELECTED_COLOR: number = StyleConstants.HIGHLIGHT_COLOR; + static MENU_SELECTED_FONT_COLOR: number = StyleConstants.LIGHT_TEXT_COLOR; + static BRUSH_COLOR: number = 0xff0000; + static DROP_ACCEPT_COLOR: number = StyleConstants.HIGHLIGHT_COLOR; + static SELECTED_COLOR: number = 0xffffff; + static SELECTED_COLOR_STR: string = StyleConstants.HexToHexString(StyleConstants.SELECTED_COLOR); + static PROGRESS_BACKGROUND_COLOR: number = 0x595959; + static GRID_LINES_COLOR: number = 0x3D3D3D; + static GRID_LINES_COLOR_STR: string = StyleConstants.HexToHexString(StyleConstants.GRID_LINES_COLOR); + + static MAX_CHAR_FOR_HISTOGRAM_LABELS: number = 20; + + static OVERLAP_COLOR: number = 0x0000ff;//0x540E25; + static BRUSH_COLORS: Array<number> = new Array<number>( + 0xFFDA7E, 0xFE8F65, 0xDA5655, 0x8F2240 + ); + + static MIN_VALUE_COLOR: number = 0x373d43; //32343d, 373d43, 3b4648 + static MARGIN_BARS_COLOR: number = 0xffffff; + static MARGIN_BARS_COLOR_STR: string = StyleConstants.HexToHexString(StyleConstants.MARGIN_BARS_COLOR); + + static HISTOGRAM_WIDTH: number = 200; + static HISTOGRAM_HEIGHT: number = 150; + static PREDICTOR_WIDTH: number = 150; + static PREDICTOR_HEIGHT: number = 100; + static RAWDATA_WIDTH: number = 150; + static RAWDATA_HEIGHT: number = 100; + static FREQUENT_ITEM_WIDTH: number = 180; + static FREQUENT_ITEM_HEIGHT: number = 100; + static CORRELATION_WIDTH: number = 555; + static CORRELATION_HEIGHT: number = 390; + static PROBLEM_FINDER_WIDTH: number = 450; + static PROBLEM_FINDER_HEIGHT: number = 150; + static PIPELINE_OPERATOR_WIDTH: number = 300; + static PIPELINE_OPERATOR_HEIGHT: number = 120; + static SLICE_WIDTH: number = 150; + static SLICE_HEIGHT: number = 45; + static BORDER_MENU_ITEM_WIDTH: number = 50; + static BORDER_MENU_ITEM_HEIGHT: number = 30; + + + static SLICE_BG_COLOR: string = StyleConstants.HexToHexString(StyleConstants.OPERATOR_BACKGROUND_COLOR); + static SLICE_EMPTY_COLOR: number = StyleConstants.OPERATOR_BACKGROUND_COLOR; + static SLICE_OCCUPIED_COLOR: number = 0xffffff; + static SLICE_OCCUPIED_BG_COLOR: string = StyleConstants.HexToHexString(StyleConstants.OPERATOR_BACKGROUND_COLOR); + static SLICE_HOVER_BG_COLOR: string = StyleConstants.HexToHexString(StyleConstants.HIGHLIGHT_COLOR); + static SLICE_HOVER_COLOR: number = 0xffffff; + + static HexToHexString(hex: number): string { + if (hex === undefined) { + return "#000000"; + } + var s = hex.toString(16); + while (s.length < 6) { + s = "0" + s; + } + return "#" + s; + } + + +} diff --git a/src/client/northstar/utils/Utils.ts b/src/client/northstar/utils/Utils.ts new file mode 100644 index 000000000..b35dce820 --- /dev/null +++ b/src/client/northstar/utils/Utils.ts @@ -0,0 +1,75 @@ +import { IBaseBrushable } from '../core/brusher/IBaseBrushable' +import { IBaseFilterConsumer } from '../core/filter/IBaseFilterConsumer' +import { IBaseFilterProvider } from '../core/filter/IBaseFilterProvider' +import { AggregateFunction } from '../model/idea/idea' + +export class Utils { + + public static EqualityHelper(a: Object, b: Object): boolean { + if (a === b) return true; + if (a === undefined && b !== undefined) return false; + if (a === null && b !== null) return false; + if (b === undefined && a !== undefined) return false; + if (b === null && a !== null) return false; + if ((<any>a).constructor.name !== (<any>b).constructor.name) return false; + return true; + } + + public static LowercaseFirstLetter(str: string) { + return str.charAt(0).toUpperCase() + str.slice(1); + } + + // + // this Type Guard tests if dropTarget is an IDropTarget. If it is, it coerces the compiler + // to treat the dropTarget parameter as an IDropTarget *ouside* this function scope (ie, in + // the scope of where this function is called from). + // + + public static isBaseBrushable<T>(obj: Object): obj is IBaseBrushable<T> { + let typed = <IBaseBrushable<T>>obj; + return typed != null && typed.BrusherModels !== undefined; + } + + public static isBaseFilterProvider(obj: Object): obj is IBaseFilterProvider { + let typed = <IBaseFilterProvider>obj; + return typed != null && typed.FilterModels !== undefined; + } + + public static isBaseFilterConsumer(obj: Object): obj is IBaseFilterConsumer { + let typed = <IBaseFilterConsumer>obj; + return typed != null && typed.FilterOperand !== undefined; + } + + public static EncodeQueryData(data: any): string { + const ret = []; + for (let d in data) { + ret.push(encodeURIComponent(d) + "=" + encodeURIComponent(data[d])); + } + return ret.join("&"); + } + + public static ToVegaAggregationString(agg: AggregateFunction): string { + if (agg === AggregateFunction.Avg) { + return "average"; + } + else if (agg === AggregateFunction.Count) { + return "count"; + } + else { + return ""; + } + } + + public static GetQueryVariable(variable: string) { + let query = window.location.search.substring(1); + let vars = query.split("&"); + for (let i = 0; i < vars.length; i++) { + let pair = vars[i].split("="); + if (decodeURIComponent(pair[0]) == variable) { + return decodeURIComponent(pair[1]); + } + } + return undefined; + } +} + |
