@obliczeniowo/elementary
Version:
Library made in Angular version 20
768 lines (754 loc) • 25.5 kB
JavaScript
import { ElementaryMath } from '@obliczeniowo/elementary/math';
class ColorRGB {
r = 0;
g = 0;
b = 0;
a = 255;
/**
* Calculate color with linear interpolation using factor parameter
*/
static proportionalColor(firstColor, secondColor, factor) {
factor = Math.min(1, Math.max(0, factor));
return new ColorRGB(firstColor.red * factor + secondColor.red * (1 - factor), firstColor.green * factor + secondColor.green * (1 - factor), firstColor.blue * factor + secondColor.blue * (1 - factor));
}
static fromHex(hexColor) {
const color = hexColor.slice(1);
const red = parseInt(color.substr(0, 2), 16);
const green = parseInt(color.substr(2, 2), 16);
const blue = parseInt(color.substr(4, 2), 16);
return new ColorRGB(red, green, blue);
}
static fromHtmlColor(htmlColor) {
const d = document.createElement('div');
d.style.color = htmlColor;
window.document.body.appendChild(d);
const parts = (window.getComputedStyle(d).color || '').match(/\d+/g) || ['00', '00', '00'];
const f = (n) => { return parseInt(n, 10); };
window.document.body.removeChild(d);
return new ColorRGB(f(parts[0]), f(parts[1]), f(parts[2]));
}
constructor(red, green, blue, alpha = 255) {
this.red = +red;
this.green = +green;
this.blue = +blue;
this.a = +alpha;
}
getHex(value) {
return ('0' + value.toString(16)).substr(-2);
}
/** set red value of color with handling min/max */
set red(red) {
this.r = ElementaryMath.minmax(Math.round(red), 0, 255);
}
get red() {
return this.r;
}
/** set green value of color with handling min/max */
set green(green) {
this.g = ElementaryMath.minmax(Math.round(green), 0, 255);
}
get green() {
return this.g;
}
/** set blue value of color with handling min/max */
set blue(blue) {
this.b = ElementaryMath.minmax(Math.round(blue), 0, 255);
}
get blue() {
return this.b;
}
get alpha() {
return this.a;
}
/** set alpha value of color with handling min/max */
set alpha(alpha) {
this.a = ElementaryMath.minmax(Math.floor(alpha), 0, 255);
}
/**
* Return color in hex format including transparency
* @return - format #ffccbbaa (last one is alpha)
*/
get getHexColor() {
return this.getHexColorBase + this.getHex(Math.round(this.a));
}
/**
* Return hex format of color without alpha
* @return color in format: #ff00bb
*/
get getHexColorBase() {
return '#' + this.getHex(this.r) + this.getHex(this.g) + this.getHex(this.b);
}
/**
* Return rgba(100, 200, 255, 0.5) color format as string
*/
getRGBAColor() {
return 'rgba(' + this.r + ', ' + this.g + ', ' + this.b + ', ' + (this.a / 255).toString().substring(0, 5) + ')';
}
toString() {
return this.getHexColorBase;
}
/**
* create object copy
*/
copy() {
return new ColorRGB(this.red, this.green, this.blue, this.alpha);
}
/**
* Multiply every single value of colors by factor parameter with handling min/max cases
*/
multiply(factor) {
return new ColorRGB(this.r * factor, this.g * factor, this.b * factor, this.alpha);
}
negative() {
return new ColorRGB(255 - this.r, 255 - this.b, 255 - this.g, this.alpha);
}
}
class ColorHSV {
h;
s;
v;
constructor(h, s, v) {
this.h = Math.max(Math.min(ColorHSV.fmod(h, 360), 360), 0);
this.s = Math.max(Math.min(s, 1), 0);
this.v = Math.max(Math.min(v, 255), 0);
}
/**
* Calculate color with linear interpolation using factor parameter
*/
static proportionalColor(firstColor, secondColor, factor) {
factor = Math.min(1, Math.max(0, factor));
return ColorHSV.createColorHSV(firstColor.h * factor + secondColor.h * (1 - factor), firstColor.s * factor + secondColor.s * (1 - factor), firstColor.v * factor + secondColor.v * (1 - factor));
}
/**
* Static constructor for creating ColorHSV object
* @param h hue - value from 0 - 360 degrees
* @param s saturation - value from 0 - 1
* @param v value 0 - 255
* @returns ColorHSV object
*/
static createColorHSV(h, s, v) {
return new ColorHSV(h, s, v);
}
static fmod(a, b) {
return a - Math.floor(a / b) * b;
}
/**
* Static constructor for creating ColorHSV object
* @param colorRGB object of RGB color
* @returns brand, brand new object of ColorHSV
*/
static createColorHSVfromRGB(colorRGB) {
const x = Math.min(Math.min(colorRGB.red, colorRGB.green), colorRGB.blue);
let f;
let i;
const v = Math.max(Math.max(colorRGB.red, colorRGB.green), colorRGB.blue);
let h;
let s;
if (x === v) {
h = 0;
s = 0;
}
else {
f =
colorRGB.red === x
? colorRGB.green - colorRGB.blue
: colorRGB.green === x
? colorRGB.blue - colorRGB.red
: colorRGB.red - colorRGB.green;
i = colorRGB.red === x ? 3 : colorRGB.green === x ? 5 : 1;
h = ColorHSV.fmod((i - f / (v - x)) * 60, 360);
s = (v - x) / v;
}
return new ColorHSV(h, s, v);
}
convertToRGB() {
let i;
let f;
let p;
let q;
let t;
let r = 0;
let g = 0;
let b = 0;
let h = this.h;
const v = this.v;
const s = this.s;
if (v === 0) {
r = 0;
g = 0;
}
else {
h /= 60;
i = Math.floor(h);
f = h - i;
p = v * (1 - s);
q = v * (1 - s * f);
t = v * (1 - s * (1 - f));
if (i === 0) {
r = v;
g = t;
b = p;
}
else if (i === 1) {
r = q;
g = v;
b = p;
}
else if (i === 2) {
r = p;
g = v;
b = t;
}
else if (i === 3) {
r = p;
g = q;
b = v;
}
else if (i === 4) {
r = t;
g = p;
b = v;
}
else if (i === 5) {
r = v;
g = p;
b = q;
}
}
return new ColorRGB(r, g, b);
}
addHue(hue) {
return ColorHSV.createColorHSV(this.h + hue, this.s, this.v);
}
toString() {
return this.convertToRGB().toString();
}
}
class Dates {
static countDays(start, end) {
let iterator = new Date(start);
let i = 0;
if (start.getTime() > end.getTime()) {
throw new Error('start date must be smaller then end date');
}
while (!Dates.equalToDayLevel(iterator, end)) {
// (24 hours * 60 min * 60 sec * 1000 ms * 2) in dividing part
const dt = Math.ceil((end.getTime() - iterator.getTime()) / 172800000);
iterator.setDate(iterator.getDate() + dt);
i += dt;
}
return i;
}
static countWeeks(start, end) {
return (end.getTime() - start.getTime()) / 604800000;
}
static getWeekDate(date, type = 'first', offset = 0) {
const weekDay = (date.getDay() + offset) % 7;
return new Date(date.getTime() + (type === 'first' ? -1 : 1) * 86400000 * weekDay);
}
static equalToDayLevel(first, last) {
return first.getMonth() === last.getMonth() && first.getDate() === last.getDate() && first.getFullYear() === last.getFullYear();
}
static equalToTimeLevel(first, last) {
return first.getHours() === last.getHours() && first.getMinutes() === last.getMinutes() && first.getSeconds() === last.getSeconds();
}
static compare(first, last, operator, error) {
switch (operator) {
case '<':
return first < last;
case '<=':
return first <= last;
case '=':
return first === last;
case '>=':
return first >= last;
case '>':
return first > last;
case '!=':
return first !== last;
default:
throw error;
}
}
static compareToTimeLevel(first, last, operator) {
const f = Dates.setDateToZero(new Date(first)).getTime();
const l = Dates.setDateToZero(new Date(last)).getTime();
return Dates.compare(f, l, operator, new Error('Wrong operator in Dates.compareToTimeLevel method'));
}
/**
* Compare two dates on time level that consider only hours, minutes and seconds (no milliseconds)
*/
static compareToDateLevel(first, last, operator) {
const f = Dates.setTimeToZero(new Date(first)).getTime();
const l = Dates.setTimeToZero(new Date(last)).getTime();
return Dates.compare(f, l, operator, new Error('Wrong operator in Dates.compareToDateLevel method'));
}
/**
* set date
*/
static setTimeToZero(date) {
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
}
/**
* clear date part and milliseconds part
*/
static setDateToZero(date) {
const copy = new Date(date);
copy.setTime(Math.floor((date.getTime() % 86400000) / 1000) * 1000);
return copy;
}
static timeFromArray(time) {
const date = new Date();
date.setTime(0);
date.setHours(time[0], time[1], time[2]);
return date;
}
}
class Point2D {
x;
y;
static isCorrectPoint(point) {
return (
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
point &&
point.x !== undefined &&
point.y !== undefined &&
point.x !== null &&
point.y !== null);
}
static fromInterface(iPoint) {
return new Point2D(iPoint.x, iPoint.y);
}
/**
* Check if two lines described by pairs of points intersects with each other
* @param pt1 first line start point
* @param pt2 first line end point
* @param pt3 second line start point
* @param pt4 second line end point
* @returns true if intersection exist
*/
static linesIntersect(pt1, pt2, pt3, pt4) {
if (Math.sign(pt1.subtract(pt2).determinant(pt3.subtract(pt2))) !==
Math.sign(pt1.subtract(pt2).determinant(pt4.subtract(pt2))) &&
Math.sign(pt3.subtract(pt4).determinant(pt1.subtract(pt4))) !==
Math.sign(pt3.subtract(pt4).determinant(pt2.subtract(pt4)))) {
return true;
}
return false;
}
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
get isZeroLength() {
return this.x === 0 && this.y === 0;
}
isPtInCircle(centralPoint, ray) {
return this.subtract(centralPoint).sickLength() < ray * ray;
}
isPtInRect(x, y, width, height) {
return this.x >= x && this.x <= x + width && this.y >= y && this.y <= y + height;
}
isEqual(point2D) {
return this.x === point2D.x && this.y === point2D.y;
}
multiply(value) {
return new Point2D(value * this.x, value * this.y);
}
scalarMultiple(other) {
return this.x * other.x + this.y * other.y;
}
determinant(other) {
return this.x * other.y - this.y * other.x;
}
scale(xs, ys) {
return new Point2D(this.x * xs, this.y * ys);
}
add(point) {
return new Point2D(this.x + point.x, this.y + point.y);
}
subtract(point) {
return new Point2D(this.x - point.x, this.y - point.y);
}
length() {
return Math.sqrt(this.scalarMultiple(this));
}
/**
* Set vector length
* @param length new length to set
* @returns new instance of Point2D with new length if current length of vector is !== 0 in this case
* return degenerated { x: 0, y: 0 } vector
*/
setLength(length) {
const k = length / (this.length() || 1);
return new Point2D(k * this.x, k * this.y);
}
/**
* Calculate x * x + y * y = scalarMultiple(this)
* @returns return sick length that can be useful to compare distance between points without calculate sqrt.
* for example if you have two points P1, P2 then P1.sickLength() < P2.sickLength() means P1 is closer to beginning of
* x = 0, y = 0 point then P2 (not require calculate sqrt)
*/
sickLength() {
return this.scalarMultiple(this);
}
angle(reverseX = false) {
return Math.atan2(this.y, reverseX ? -this.x : this.x);
}
/**
* rotate point by angle
* @param angle - in radians
* @returns new Instance of Point2D
*/
rotate(angle) {
const sin = Math.sin(angle);
const cos = Math.cos(angle);
return new Point2D(this.x * cos - this.y * sin, this.x * sin + this.y * cos);
}
/**
* Calculate Point2D containing coordinates of point thrown perpendicularly on line
* @param startLinePoint first point of line
* @param endLinePoint last point of line
* @returns if points are equal then null, if not then new instance of Point2D containing coordinates of point thrown
* perpendicularly on line
*/
getPointOnLine(startLinePoint, endLinePoint) {
if (startLinePoint.isEqual(endLinePoint)) {
return null;
}
const lineVector = startLinePoint.subtract(endLinePoint);
const u = this.subtract(startLinePoint).scalarMultiple(startLinePoint.subtract(endLinePoint)) / lineVector.scalarMultiple(lineVector);
return startLinePoint.add(lineVector.multiply(u));
}
/**
* Method to draw point position using IDrawingInterface (require only DrawText method)
* @param ctx can be any IDrawingInterface or your own class containing drawing method
* @param options options to control drawing:
* x, y - position coordinates to display (can be different then coordinates)
* precision - 2 for value 2.3333 display 2.33
* color - of text
* angle - of text
*/
drawPointPos(ctx, options) {
ctx.setFontSize(options?.fontSize || 5);
ctx.drawText(`X: ${this.x.toFixed(options?.precision || 3)}; Y: ${this.y.toFixed(options?.precision || 3)}`, new Point2D(options?.x || this.x, options?.y || this.y), options?.color || 'black', options?.angle || 0);
}
/**
* Make brand brand new copy of object
* @returns new instance
*/
copy() {
return new Point2D(this.x, this.y);
}
/**
* Simplify to interface
*
* @returns IPoint2D interface
*/
toInterface() {
return { x: this.x, y: this.y };
}
/**
* Convert to string
* @param separator separator to use for coordinates
* @returns string in format: ' 10, 23.5'
*/
toString(separator = ',') {
return ' ' + this.x + separator + this.y;
}
toCssTranslate(unit = '') {
return `translate(${this.x}${unit}, ${this.y}${unit})`;
}
}
class Point2DData extends Point2D {
extData;
constructor(x = 0, y = 0, extData) {
super(x, y);
this.extData = extData;
}
}
class Point3D {
x = 0;
y = 0;
z = 0;
constructor(x = 0, y = 0, z = 0) {
this.x = x;
this.y = y;
this.z = z;
}
add(point) {
return new Point3D(this.x + point.x, this.y + point.y, this.z + point.z);
}
subtract(point) {
return new Point3D(this.x - point.x, this.y - point.y, this.z - point.z);
}
sickLength() {
return this.x * this.x + this.y * this.y + this.z * this.z;
}
length() {
return Math.sqrt(this.sickLength());
}
copy(p3d) {
return new Point3D(p3d.x, p3d.y, p3d.z);
}
}
/* eslint-disable @typescript-eslint/non-nullable-type-assertion-style */
class TextTransform {
/**
* Split text only on space sign and if length of more then 1 word in one line is greater then maxLength
* @param text - text to split
* @param maxLength - max length of line
* @returns table of splitted text
*/
static splitByLengthAndWords(text, maxLength) {
const strings = text.split(' ');
const arr = [];
arr[0] = strings.shift() || '';
while (strings.length) {
if (arr[arr.length - 1].length + 1 + strings[0].length < maxLength) {
arr[arr.length - 1] = arr[arr.length - 1] + strings.shift();
}
else {
arr.push(strings.shift());
}
}
return arr;
}
/**
* Transform text `some text {to-replace-1}, by using other object {to-replace-2}` using object properties keys
* to replace in text by given properties object
* @param text - text with hidden {key} value
* @param properties - object properties
* @returns text with replaced by keys part, if key not exist in properties object then matching text is put there
*/
static prepare(text, properties, prepareProps = (key, properties) => properties[key]?.toString()) {
const pattern = /{(\w+)}/g;
const replacePlaceholders = (match, fieldName) => {
const fieldValue = prepareProps(fieldName, properties);
return fieldValue !== undefined ? String(fieldValue) : match;
};
return text.replace(pattern, replacePlaceholders);
}
static splitByMathOperator(text) {
const operators = ['+', '-', '*', '/', '%', '^', '(', ')'];
const operatorTable = [];
let currentFragment = '';
for (let i = 0; i < text.length; i++) {
const char = text.charAt(i);
if (operators.includes(char)) {
if (currentFragment !== '') {
operatorTable.push(currentFragment);
currentFragment = '';
}
operatorTable.push(char);
}
else {
currentFragment += char;
}
}
if (currentFragment !== '') {
operatorTable.push(currentFragment);
}
return operatorTable;
}
}
class LinearFunction2D {
a;
b;
static fromTwoPoints(p1, p2) {
// if pt.x === p2.x then there is no way to define proper f(x) function
if (p1.x === p2.x) {
return undefined;
}
const a = (p2.y - p1.y) / (p2.x - p1.x);
const b = p1.y - a * p1.x;
return new LinearFunction2D(a, b);
}
constructor(a, b) {
this.a = a;
this.b = b;
}
zeroPoint() {
const { a, b } = this;
return -b / a;
}
xPoint(y) {
const { a, b } = this;
return (y - b) / a;
}
yPoint(x) {
const { a, b } = this;
return x * a + b;
}
}
class AbstractSearchDomain {
items = [];
regExpOn = false;
filtered = [];
}
class DefaultSearchDomain extends AbstractSearchDomain {
constructor(items) {
super();
this.items = items;
this.filtered = [...this.items];
}
search(filter) {
if (filter?.length) {
filter = filter.toLowerCase();
if (this.regExpOn) {
const reg = new RegExp(filter, 'i');
this.filtered = this.items.filter(item => reg.test(item.text));
}
else {
this.filtered = this.items.filter(item => item.text.toLocaleLowerCase().includes(filter));
}
}
else {
this.filtered = [...this.items];
}
}
}
class Rectangle {
width = 0;
height = 0;
start;
end;
constructor(start = new Point2D(), width = 0, height = 0) {
Object.assign(this, { start, width, height });
}
get left() {
return this.start.x;
}
get bottom() {
return this.start.y;
}
get top() {
return this.start.y + this.height;
}
get right() {
return this.start.x + this.width;
}
isPointIn(point = new Point2D()) {
const { left, right, bottom, top } = this;
if (point.x >= left &&
point.x <= right &&
point.y >= bottom &&
point.y <= top) {
return true;
}
return false;
}
isRectanglesOverlapping(rectangle = new Rectangle()) {
if (this.isPointIn(rectangle.start) ||
this.isPointIn(new Point2D(rectangle.right, rectangle.bottom)) ||
this.isPointIn(new Point2D(rectangle.right, rectangle.top)) ||
this.isPointIn(new Point2D(rectangle.left, rectangle.top)) ||
rectangle.isPointIn(this.start) ||
rectangle.isPointIn(new Point2D(this.right, this.bottom)) ||
rectangle.isPointIn(new Point2D(this.right, this.top)) ||
rectangle.isPointIn(new Point2D(this.left, this.top)) ||
Point2D.linesIntersect(this.start, new Point2D(this.right, this.top), rectangle.start, new Point2D(rectangle.right, rectangle.top))) {
return true;
}
return false;
}
drawRectangle(ctx, stroke, strokeColor, fillColor) {
const { start, width, height } = this;
ctx.drawRect(start.x, start.y, width, height, stroke, strokeColor, fillColor);
}
}
class Polygon {
static random = (spaceWidth, spaceHeight, size = 20) => {
const maxRadius = size;
const points = [];
const center = new Point2D(Math.random() * (spaceWidth - 2 * maxRadius) + maxRadius, Math.random() * (spaceHeight - 2 * maxRadius) + maxRadius);
const nrOfPoints = Math.ceil(Math.random() * 5) + 5;
for (let i = 0; i < nrOfPoints; i++) {
const angle = (i / nrOfPoints) * Math.PI * 2;
const radius = Math.random() * maxRadius;
points.push(new Point2D(radius * Math.cos(angle) + center.x, radius * Math.sin(angle) + center.y));
}
return new Polygon(points);
};
points = [];
constructor(points = []) {
this.points = points;
}
getBondingRect() {
const polygon = this.points;
// detecting if in bounding box
let minX = polygon[0].x;
let maxX = polygon[0].x;
let minY = polygon[0].y;
let maxY = polygon[0].y;
for (let i = 1; i < polygon.length; i++) {
const q = polygon[i];
minX = Math.min(q.x, minX);
maxX = Math.max(q.x, maxX);
minY = Math.min(q.y, minY);
maxY = Math.max(q.y, maxY);
}
return new Rectangle(new Point2D(minX, minY), maxX - minX, maxY - minY);
}
isPointIn(point) {
const polygon = this.points;
// detecting if in bounding box
let minX = polygon[0].x;
let maxX = polygon[0].x;
let minY = polygon[0].y;
let maxY = polygon[0].y;
for (let i = 1; i < polygon.length; i++) {
const q = polygon[i];
minX = Math.min(q.x, minX);
maxX = Math.max(q.x, maxX);
minY = Math.min(q.y, minY);
maxY = Math.max(q.y, maxY);
}
if (point.x < minX || point.x > maxX || point.y < minY || point.y > maxY) {
return false;
}
// https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html
let inside = false;
let j = polygon.length - 1;
for (let i = 0; i < polygon.length; j = i++) {
if (polygon[i].y > point.y !== polygon[j].y > point.y &&
point.x <
((polygon[j].x - polygon[i].x) * (point.y - polygon[i].y)) /
(polygon[j].y - polygon[i].y) +
polygon[i].x) {
inside = !inside;
}
}
return inside;
}
isIntersect(polygon = new Polygon()) {
const { points } = this;
const { points: pPoints } = polygon;
for (let start = 0, end = points.length - 1; start < points.length - 1; end = start++) {
const fp = points[start];
const lp = points[end];
for (let pStart = 0, pEnd = pPoints.length - 1; pStart < pPoints.length - 1; pEnd = pStart++) {
const pfp = pPoints[pStart];
const plp = pPoints[pEnd];
if (Point2D.linesIntersect(fp, lp, pfp, plp)) {
return true;
}
}
}
return false;
}
subtract(point) {
return new Polygon(this.points.map((pt) => pt.subtract(point)));
}
multiply(scalar) {
return new Polygon(this.points.map((point) => point.multiply(scalar)));
}
drawPolygon(ctx, stroke, color) {
ctx.drawPolyline(this.points, stroke, color);
}
}
/**
* Generated bundle index. Do not edit.
*/
export { AbstractSearchDomain, ColorHSV, ColorRGB, Dates, DefaultSearchDomain, LinearFunction2D, Point2D, Point2DData, Point3D, Polygon, Rectangle, TextTransform };
//# sourceMappingURL=obliczeniowo-elementary-classes.mjs.map