Skip to content

Commit

Permalink
Merge pull request #185 from VisActor/feat/add-some-utils
Browse files Browse the repository at this point in the history
Feat/add some utils
  • Loading branch information
xile611 authored May 17, 2024
2 parents 394ffde + 7c8aba2 commit 70ec429
Show file tree
Hide file tree
Showing 15 changed files with 221 additions and 65 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"comment": "feat: add some utils, `isDataView`, `normalizeAngle`, .etc\n\n",
"type": "none",
"packageName": "@visactor/vdataset"
}
],
"packageName": "@visactor/vdataset",
"email": "dingling112@gmail.com"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"comment": "feat: add some utils, `isDataView`, `normalizeAngle`, .etc\n\n",
"type": "none",
"packageName": "@visactor/vutils"
}
],
"packageName": "@visactor/vutils",
"email": "dingling112@gmail.com"
}
10 changes: 10 additions & 0 deletions packages/vdataset/__tests__/dataview.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { isDataView, DataView, DataSet } from '../src';

describe('Dataview', () => {
it('isDataView', () => {
const dt = new DataSet();

expect(isDataView(new DataView(dt))).toBeTruthy();
expect(isDataView({})).toBeFalsy();
});
});
4 changes: 4 additions & 0 deletions packages/vdataset/src/data-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,3 +406,7 @@ export class DataView {
this.target = null;
}
}

export function isDataView(obj: any): obj is DataView {
return obj instanceof DataView;
}
2 changes: 1 addition & 1 deletion packages/vdataset/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export type { IDataViewParserOptions } from './parser/data-view';

// core
export { DataSet } from './data-set';
export { DataView } from './data-view';
export { DataView, isDataView } from './data-view';
export type { IDataViewOptions, IFields, IFieldsMeta } from './data-view';
export type { Parser, IParserOptions } from './parser';

Expand Down
14 changes: 14 additions & 0 deletions packages/vutils/__tests__/angle.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { normalizeAngle } from '../src';

describe('angle utils', () => {
it('normalizeAngle', () => {
expect(normalizeAngle(0)).toBe(0);
expect(normalizeAngle(Math.PI)).toBe(Math.PI);
expect(normalizeAngle(2 * Math.PI)).toBe(0);
expect(normalizeAngle(6.28)).toBe(6.28);
expect(normalizeAngle(3 * Math.PI)).toBe(Math.PI);
expect(normalizeAngle(4 * Math.PI)).toBe(0);
expect(normalizeAngle(-2 * Math.PI)).toBe(0);
expect(normalizeAngle(-Math.PI)).toBe(Math.PI);
});
});
37 changes: 34 additions & 3 deletions packages/vutils/__tests__/common/bisect.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { bisect } from '../../src';
import { bisect, findZeroOfFunction, binaryFuzzySearch } from '../../src';

const values = [1, 5, 7, 9, 10, 20];

Expand All @@ -11,8 +11,12 @@ describe('bisect', () => {
expect(bisect(values, 1)).toBe(1);
});

it('bisect(values, -1) should be 1', () => {
expect(bisect(values, 1)).toBe(1);
it('bisect(values, -1) should be 0', () => {
expect(bisect(values, -1)).toBe(0);
});

it('bisect(values, 20) should be 6', () => {
expect(bisect(values, 20)).toBe(6);
});

it('bisect(values, 100) should be 6', () => {
Expand All @@ -23,3 +27,30 @@ describe('bisect', () => {
expect(bisect(values, 100, 3, 6)).toBe(6);
});
});

describe('findZeroOfFunction', () => {
it('findZeroOfFunction()', () => {
expect(findZeroOfFunction((entry: number) => entry - 5, 0, 100)).toBeCloseTo(5);
});
});

describe('binaryFuzzySearch', () => {
it('binaryFuzzySearch(5)', () => {
expect(binaryFuzzySearch(values, (entry: number) => entry - 5)).toBe(1);
});
it('binaryFuzzySearch(2)', () => {
expect(binaryFuzzySearch(values, (entry: number) => entry - 2)).toBe(1);
});
it('binaryFuzzySearch(1)', () => {
expect(binaryFuzzySearch(values, (entry: number) => entry - 1)).toBe(0);
});
it('binaryFuzzySearch(-1)', () => {
expect(binaryFuzzySearch(values, (entry: number) => entry + 1)).toBe(0);
});
it('binaryFuzzySearch(100)', () => {
expect(binaryFuzzySearch(values, (entry: number) => entry - 100)).toBe(6);
});
it('binaryFuzzySearch(20)', () => {
expect(binaryFuzzySearch(values, (entry: number) => entry - 20)).toBe(5);
});
});
6 changes: 5 additions & 1 deletion packages/vutils/__tests__/dom.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { styleStringToObject, lowerCamelCaseToMiddle } from '../src';
import { styleStringToObject, lowerCamelCaseToMiddle, isHTMLElement } from '../src';

describe('dom utils', () => {
it('styleStringToObject', () => {
Expand All @@ -14,4 +14,8 @@ describe('dom utils', () => {
expect(lowerCamelCaseToMiddle('lineHeight')).toBe('line-height');
expect(lowerCamelCaseToMiddle('fontSize')).toBe('font-size');
});

it('isHTMLElement', () => {
expect(isHTMLElement({})).toBeFalsy();
});
});
14 changes: 14 additions & 0 deletions packages/vutils/src/angle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,17 @@ export function polarToCartesian(center: IPointLike, radius: number, angleInRadi
export function getAngleByPoint(center: IPointLike, point: IPointLike): number {
return Math.atan2(point.y - center.y, point.x - center.x);
}

/**
* 角度标准化处理
* @param angle 弧度角
*/
export function normalizeAngle(angle: number): number {
while (angle < 0) {
angle += Math.PI * 2;
}
while (angle >= Math.PI * 2) {
angle -= Math.PI * 2;
}
return angle;
}
92 changes: 92 additions & 0 deletions packages/vutils/src/common/bisect.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { ascending } from './ascending';
import isNil from './isNil';
import { Logger } from '../logger';

/**
* 通过二分法,查找数组a中大于数值x的第一个元素的序号
* @param a
* @param x
* @param lo
* @param hi
* @returns
*/
export function bisect(a: number[], x: number, lo: number = 0, hi?: number) {
if (isNil(hi)) {
hi = a.length;
Expand All @@ -15,3 +24,86 @@ export function bisect(a: number[], x: number, lo: number = 0, hi?: number) {
}
return lo;
}

/* Adapted from fmin by Ben Frederickson
* https://github.com/benfred/fmin
* Licensed under the BSD-3-Clause
* url: https://github.com/benfred/fmin/blob/master/src/bisect.js
* License: https://github.com/benfred/fmin/blob/master/LICENSE
* @license
*/
/** finds the zeros of a function, given two starting points (which must
* have opposite signs */
export function findZeroOfFunction(
f: (entry: number) => number,
a: number,
b: number,
parameters?: {
maxIterations?: number;
tolerance?: number;
}
) {
const maxIterations = parameters?.maxIterations ?? 100;
const tolerance = parameters?.tolerance ?? 1e-10;
const fA = f(a);
const fB = f(b);
let delta = b - a;

if (fA * fB > 0) {
const logger = Logger.getInstance();
logger.error('Initial bisect points must have opposite signs');
return NaN;
}

if (fA === 0) {
return a;
}
if (fB === 0) {
return b;
}

for (let i = 0; i < maxIterations; ++i) {
delta /= 2;
const mid = a + delta;
const fMid = f(mid);

if (fMid * fA >= 0) {
a = mid;
}

if (Math.abs(delta) < tolerance || fMid === 0) {
return mid;
}
}
return a + delta;
}

/**
* 二分靠近框架,返回数组中第一个大于等于目标值的数的索引
* @param arr 数组
* @param compareFn 比较函数,返回(当前值-目标值)
*/
export const binaryFuzzySearch = <T>(arr: T[], compareFn: (value: T) => number) => {
return binaryFuzzySearchInNumberRange(0, arr.length, value => compareFn(arr[value]));
};

/**
* 二分靠近框架,返回数字区间中第一个大于等于目标值的数字
* @param x1 区间上界
* @param x2 区间下界(不包含)
* @param compareFn 比较函数,返回(当前值-目标值)
*/
export const binaryFuzzySearchInNumberRange = (x1: number, x2: number, compareFn: (value: number) => number) => {
let left = x1;
let right = x2;
while (left < right) {
const mid = Math.floor((left + right) / 2);
if (compareFn(mid) >= 0) {
right = mid; // 第一个大于等于目标值的数
} else {
left = mid + 1;
}
}
return left;
};
2 changes: 1 addition & 1 deletion packages/vutils/src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export * from './array';
export { range } from './range';
export { ascending } from './ascending';
export * from './quantileSorted';
export { bisect } from './bisect';
export { bisect, findZeroOfFunction, binaryFuzzySearch, binaryFuzzySearchInNumberRange } from './bisect';
export { deviation } from './deviation';
export { median } from './median';
export { variance } from './variance';
Expand Down
18 changes: 18 additions & 0 deletions packages/vutils/src/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,21 @@ export const styleStringToObject = (styleStr: string = '') => {
export const lowerCamelCaseToMiddle = (str: string) => {
return str.replace(/([A-Z])/g, '-$1').toLowerCase();
};

export function isHTMLElement(obj: any): obj is Element {
try {
return obj instanceof Element;
} catch {
// 跨端 plan B
const htmlElementKeys: (keyof Element)[] = [
'children',
'innerHTML',
'classList',
'setAttribute',
'tagName',
'getBoundingClientRect'
];
const keys = Object.keys(obj);
return htmlElementKeys.every(key => keys.includes(key));
}
}
49 changes: 0 additions & 49 deletions packages/vutils/src/fmin/bisect.ts

This file was deleted.

1 change: 0 additions & 1 deletion packages/vutils/src/fmin/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * from './blas1';
export * from './nelder-mead';
export * from './conjugate-gradient';
export * from './bisect';
15 changes: 6 additions & 9 deletions packages/vutils/src/graphics/algorithm/intersect.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* 用于判断2d相交 */
import { degreeToRadian } from '../../angle';
import type { IBoundsLike } from '../../data-structure';
import type { vec2 } from '../../math';
import { pi2, halfPi, crossProduct, fuzzyEqualVec } from '../../math';
Expand Down Expand Up @@ -291,10 +292,6 @@ function rotate({ x, y }: Point, deg: number, origin = { x: 0, y: 0 }) {
};
}

function toDeg(angle: number) {
return (angle / 180) * Math.PI;
}

function getCenterPoint(box: RotateBound): Point {
return {
x: (box.x1 + box.x2) / 2,
Expand All @@ -312,7 +309,7 @@ interface RotateBound extends IBoundsLike {
* @param {Object} box
*/
function toRect(box: RotateBound, isDeg: boolean) {
const deg = isDeg ? box.angle : toDeg(box.angle);
const deg = isDeg ? box.angle : degreeToRadian(box.angle);
const cp = getCenterPoint(box);
return [
rotate(
Expand Down Expand Up @@ -406,11 +403,11 @@ export function isRotateAABBIntersect(
const B1C1 = vector(rect2[1], rect2[2]);

// 矩形1 的两个弧度
const deg11 = isDeg ? box1.angle : toDeg(box1.angle);
let deg12 = isDeg ? box1.angle + halfPi : toDeg(90 - box1.angle);
const deg11 = isDeg ? box1.angle : degreeToRadian(box1.angle);
let deg12 = isDeg ? box1.angle + halfPi : degreeToRadian(90 - box1.angle);
// 矩形2 的两个弧度
const deg21 = isDeg ? box2.angle : toDeg(box2.angle);
let deg22 = isDeg ? box2.angle + halfPi : toDeg(90 - box2.angle);
const deg21 = isDeg ? box2.angle : degreeToRadian(box2.angle);
let deg22 = isDeg ? box2.angle + halfPi : degreeToRadian(90 - box2.angle);
if (deg12 > pi2) {
deg12 -= pi2;
}
Expand Down

0 comments on commit 70ec429

Please sign in to comment.