aboutsummaryrefslogtreecommitdiff
path: root/src/client/northstar/utils
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/northstar/utils')
-rw-r--r--src/client/northstar/utils/ArrayUtil.ts90
-rw-r--r--src/client/northstar/utils/Extensions.ts29
-rw-r--r--src/client/northstar/utils/GeometryUtil.ts129
-rw-r--r--src/client/northstar/utils/IDisposable.ts3
-rw-r--r--src/client/northstar/utils/IEquatable.ts3
-rw-r--r--src/client/northstar/utils/KeyCodes.ts137
-rw-r--r--src/client/northstar/utils/LABColor.ts90
-rw-r--r--src/client/northstar/utils/MathUtil.ts241
-rw-r--r--src/client/northstar/utils/PartialClass.ts7
-rw-r--r--src/client/northstar/utils/SizeConverter.ts99
-rw-r--r--src/client/northstar/utils/StyleContants.ts95
-rw-r--r--src/client/northstar/utils/Utils.ts75
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;
+ }
+}
+