diff --git a/packages/vectors/src/api.ts b/packages/vectors/src/api.ts index 24ba28ca84..51b631a0f9 100644 --- a/packages/vectors/src/api.ts +++ b/packages/vectors/src/api.ts @@ -306,3 +306,29 @@ export const Z4: ReadonlyVec = Object.freeze([0, 0, 1, 0]); export const W4: ReadonlyVec = Object.freeze([0, 0, 0, 1]); export type Template = (syms: string[], i?: number) => string; + +export interface ToStringOpts { + /** + * Number of fractional digits + * + * @defaultValue 3 + */ + prec: number; + /** + * If given, each formatted vector component will be padded to given number + * of characters. + */ + width: number; + /** + * Inter-component delimiter. + * + * @defaultValue ", " + */ + delim: string; + /** + * Prefix/suffix wrapper strings. + * + * @defaultValue "[" and "]" + */ + wrap: ArrayLike; +} diff --git a/packages/vectors/src/gvec.ts b/packages/vectors/src/gvec.ts index 88f52b6f8a..c50c725a78 100644 --- a/packages/vectors/src/gvec.ts +++ b/packages/vectors/src/gvec.ts @@ -7,6 +7,7 @@ import { eqDeltaS } from "./eqdelta"; import { stridedValues } from "./internal/vec-utils"; import { zeroes } from "./setn"; import { setS } from "./sets"; +import { FORMATTER } from "./string"; const SYM_B = "buf"; const SYM_L = "length"; @@ -131,9 +132,7 @@ export const gvec = ( eqDeltaS(buf, o, size, eps, offset, 0, stride, 1); case SYM_STR: return () => - JSON.stringify([ - ...stridedValues(obj, size, offset, stride), - ]); + FORMATTER(stridedValues(obj, size, offset, stride)); default: const j = parseInt(id); return !isNaN(j) && j >= 0 && j < size diff --git a/packages/vectors/src/internal/avec.ts b/packages/vectors/src/internal/avec.ts index 6fbf47eb3b..4e345beadf 100644 --- a/packages/vectors/src/internal/avec.ts +++ b/packages/vectors/src/internal/avec.ts @@ -1,5 +1,6 @@ import type { NumericArray } from "@thi.ng/api"; import type { StridedVec } from "../api"; +import { FORMATTER } from "../string"; export abstract class AVec implements StridedVec { buf: NumericArray; @@ -15,4 +16,8 @@ export abstract class AVec implements StridedVec { abstract get length(): number; abstract [Symbol.iterator](): IterableIterator; + + toString() { + return FORMATTER(this); + } } diff --git a/packages/vectors/src/string.ts b/packages/vectors/src/string.ts new file mode 100644 index 0000000000..7fb549fb43 --- /dev/null +++ b/packages/vectors/src/string.ts @@ -0,0 +1,63 @@ +import { isFunction } from "@thi.ng/checks"; +import { float, floatFixedWidth, Stringer } from "@thi.ng/strings"; +import type { ToStringOpts } from "./api"; + +/** + * Returns a new generic vector formatter for given options (all optional). The + * returned function accepts a single vector(like) value and returns its + * formatted string representation. + * + * @remarks + * See {@link ToStringOpts} for further details. Also see {@link setFormat} to + * set default formatter. + * + * @example + * ```ts + * defFormat()([1, -2, 3]) + * // [1.000, -2.000, 3.000] + * + * defFormat({ width: 10, wrap: "||", delim: "|\n|" })([1, -2, 3]) + * // | 1.000| + * // | -2.000| + * // | 3.000| + * + * defFormat({ prec: 5, delim: " " })([1, -2, 3]) + * // [1.00000 -2.00000 3.00000] + * ``` + * + * @param prec + * @param width + */ +export const defFormat = ( + opts?: Partial +): Stringer> => { + const { prec, width, delim, wrap } = { + prec: 3, + delim: ", ", + wrap: "[]", + ...opts, + }; + const fmt = width ? floatFixedWidth(width, prec) : float(prec); + return (src) => { + let res: string[] = []; + for (let x of src) res.push(fmt(x)); + return `${wrap[0]}${res.join(delim)}${wrap[1]}`; + }; +}; + +/** + * Sets package-wide default vector formatter. See {@link defFormat}, + * {@link FORMATTER}. + * + * @param fmt + */ +export const setFormat = ( + fmt: Stringer> | Partial +) => { + FORMATTER = isFunction(fmt) ? fmt : defFormat(fmt); +}; + +/** + * Package-wide default vector formatter. + */ +export let FORMATTER: Stringer> = defFormat(); diff --git a/packages/vectors/src/vec2.ts b/packages/vectors/src/vec2.ts index e9b785e09c..476fc6330b 100644 --- a/packages/vectors/src/vec2.ts +++ b/packages/vectors/src/vec2.ts @@ -124,10 +124,6 @@ export class Vec2 extends AVec implements IHash, IVector { toJSON() { return [this.x, this.y]; } - - toString() { - return `[${this.x}, ${this.y}]`; - } } declareIndices(Vec2.prototype, ["x", "y"]); diff --git a/packages/vectors/src/vec3.ts b/packages/vectors/src/vec3.ts index fefd28106a..b8e9f78095 100644 --- a/packages/vectors/src/vec3.ts +++ b/packages/vectors/src/vec3.ts @@ -127,10 +127,6 @@ export class Vec3 extends AVec implements IHash, IVector { toJSON() { return [this.x, this.y, this.z]; } - - toString() { - return `[${this.x}, ${this.y}, ${this.z}]`; - } } declareIndices(Vec3.prototype, ["x", "y", "z"]); diff --git a/packages/vectors/src/vec4.ts b/packages/vectors/src/vec4.ts index 9d7ea6cd1c..ce9a71476d 100644 --- a/packages/vectors/src/vec4.ts +++ b/packages/vectors/src/vec4.ts @@ -128,10 +128,6 @@ export class Vec4 extends AVec implements IHash, IVector { toJSON() { return [this.x, this.y, this.z, this.w]; } - - toString() { - return `[${this.x}, ${this.y}, ${this.z}, ${this.w}]`; - } } declareIndices(Vec4.prototype, ["x", "y", "z", "w"]);