Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
# Change logs for Labella.js

## v2.x.x

### 2.0.0 (2026-05-31)

Complete rewrite in TypeScript with a modern toolchain. No API changes — all existing code using `labella.Node`, `labella.Force`, `labella.Distributor`, `labella.Renderer`, `labella.metrics` and `labella.util` continues to work unchanged.

#### Toolchain

- Replace Gulp + webpack + Karma with **Rollup + Vitest + Vite**
- Build outputs: `labella.js` (ESM), `labella.cjs` (CJS), `labella.umd.js`, `labella.umd.min.js`, `labella.d.ts` (bundled type declarations)
- `"type": "module"` — package is now pure ESM-first with CJS interop via exports map

#### TypeScript migration

- All `src/core/` and `src/lib/` files converted to TypeScript with strict mode enabled
- Native type declarations included — no `@types/labella` needed
- `vpsc` vendored from WebCola as `src/lib/vpsc.ts` — no new runtime dependency

#### Demo

- Replace Bower with CDN links at exact same dependency versions (d3 v3.5.17, d3kit v1.0.9, Angular v1.4.8, Bootstrap 3.4.1)
- `npm run dev` now serves the demo via Vite from the repo root

## v1.x.x

### 1.1.4 (2017-05-24)
Expand Down
28 changes: 28 additions & 0 deletions dist/core/distributor.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Node } from './labelNode.js';
export interface DistributorOptions {
algorithm?: string | ((nodes: Node[], options: DistributorOptions) => Node[][]);
layerWidth?: number | null;
density?: number;
nodeSpacing?: number;
stubWidth?: number;
}
declare const DEFAULT_OPTIONS: Required<Omit<DistributorOptions, 'algorithm'>> & {
algorithm: string;
};
export interface Distributor {
options(): DistributorOptions;
options(x: DistributorOptions): Distributor;
computeRequiredWidth(nodes: Node[]): number;
maxWidthPerLayer(): number;
needToSplit(nodes: Node[]): boolean;
estimateRequiredLayers(nodes: Node[]): number;
countIdealOverlaps(nodes: Node[]): Node[];
distribute(nodes: Node[]): Node[][];
}
interface DistributorConstructor {
(initOptions?: DistributorOptions): Distributor;
DEFAULT_OPTIONS: typeof DEFAULT_OPTIONS;
}
declare const Distributor: DistributorConstructor;
export { Distributor };
export default Distributor;
39 changes: 39 additions & 0 deletions dist/core/force.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { DistributorOptions } from './distributor.js';
import { Node } from './labelNode.js';
export interface ForceOptions {
nodeSpacing?: number;
minPos?: number | null;
maxPos?: number | null;
algorithm?: string | ((nodes: Node[], options: DistributorOptions) => Node[][]);
removeOverlap?: boolean;
density?: number;
stubWidth?: number;
lineSpacing?: number;
layerWidth?: number | null;
}
declare const DEFAULT_OPTIONS: {
nodeSpacing: number;
minPos: number | null;
maxPos: null;
algorithm: string;
removeOverlap: boolean;
density: number;
stubWidth: number;
lineSpacing: number;
};
export interface Force {
nodes(): Node[];
nodes(x: Node[]): Force;
getLayers(): Node[][] | null;
options(): ForceOptions;
options(x: ForceOptions): Force;
compute(): Force;
start(): void;
}
interface ForceConstructor {
(_options?: ForceOptions): Force;
DEFAULT_OPTIONS: typeof DEFAULT_OPTIONS;
}
declare const Force: ForceConstructor;
export { Force };
export default Force;
9 changes: 9 additions & 0 deletions dist/core/helper.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import extend from '../lib/extend.js';
declare const helper: {
isDefined(x: unknown): boolean;
last<T>(array: T[]): T | null;
pick<T extends object, K extends keyof T>(object: T, keys: K[]): Pick<T, K>;
sum<T>(array: T[], accessor: (item: T, index: number) => number): number;
extend: typeof extend;
};
export default helper;
41 changes: 41 additions & 0 deletions dist/core/labelNode.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
export declare class LabelNode<T = unknown> {
idealPos: number;
currentPos: number;
width: number;
data: T | undefined;
layerIndex: number;
parent: LabelNode<T> | null;
child: LabelNode<T> | null;
overlaps?: LabelNode<T>[];
overlapCount?: number;
targetPos?: number;
index?: number;
x?: number;
y?: number;
dx?: number;
dy?: number;
constructor(idealPos: number, width: number, data?: T);
/** Returns negative if nodes overlap */
distanceFrom(node: LabelNode): number;
moveToIdealPosition(): this;
displacement(): number;
overlapWithNode(node: LabelNode, buffer?: number): boolean;
overlapWithPoint(pos: number): boolean;
positionBefore(node: LabelNode, buffer?: number): number;
positionAfter(node: LabelNode, buffer?: number): number;
currentRight(): number;
currentLeft(): number;
idealRight(): number;
idealLeft(): number;
createStub(width?: number): LabelNode<T>;
removeStub(): this;
isStub(): boolean;
getPathToRoot(): LabelNode<T>[];
getPathFromRoot(): LabelNode<T>[];
getPathToRootLength(): number;
getRoot(): LabelNode<T>;
getLayerIndex(): number;
clone(): LabelNode<T>;
}
export { LabelNode as Node };
export default LabelNode;
14 changes: 14 additions & 0 deletions dist/core/metrics.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Node } from './labelNode.js';
type NodeOrLayer = Node | Node[];
type LayersInput = NodeOrLayer[];
export declare const metrics: {
displacement(nodes: LayersInput): number;
pathLength(nodes: LayersInput): number;
overflowSpace(nodes: LayersInput, minPos?: number | null, maxPos?: number | null): number;
overDensitySpace(nodes: LayersInput, density?: number | null, layerWidth?: number | null, nodeSpacing?: number): number;
overlapCount(nodes: LayersInput, buffer?: number): number;
overlapSpace(nodes: LayersInput): number;
weightedAllocation(nodes: LayersInput): number;
weightedAllocatedSpace(nodes: LayersInput): number;
};
export default metrics;
40 changes: 40 additions & 0 deletions dist/core/node.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
export declare class Node<T = unknown> {
idealPos: number;
currentPos: number;
width: number;
data: T | undefined;
layerIndex: number;
parent: Node<T> | null;
child: Node<T> | null;
overlaps?: Node<T>[];
overlapCount?: number;
targetPos?: number;
index?: number;
x?: number;
y?: number;
dx?: number;
dy?: number;
constructor(idealPos: number, width: number, data?: T);
/** Returns negative if nodes overlap */
distanceFrom(node: Node): number;
moveToIdealPosition(): this;
displacement(): number;
overlapWithNode(node: Node, buffer?: number): boolean;
overlapWithPoint(pos: number): boolean;
positionBefore(node: Node, buffer?: number): number;
positionAfter(node: Node, buffer?: number): number;
currentRight(): number;
currentLeft(): number;
idealRight(): number;
idealLeft(): number;
createStub(width?: number): Node<T>;
removeStub(): this;
isStub(): boolean;
getPathToRoot(): Node<T>[];
getPathFromRoot(): Node<T>[];
getPathToRootLength(): number;
getRoot(): Node<T>;
getLayerIndex(): number;
clone(): Node<T>;
}
export default Node;
13 changes: 13 additions & 0 deletions dist/core/removeOverlap.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Node } from './labelNode.js';
export interface RemoveOverlapOptions {
lineSpacing?: number;
nodeSpacing?: number;
minPos?: number | null;
maxPos?: number | null;
}
declare function removeOverlap(nodes: Node[], options?: RemoveOverlapOptions): Node[];
declare namespace removeOverlap {
var DEFAULT_OPTIONS: Required<RemoveOverlapOptions>;
}
export { removeOverlap };
export default removeOverlap;
27 changes: 27 additions & 0 deletions dist/core/renderer.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Node } from './labelNode.js';
export type Direction = 'down' | 'up' | 'left' | 'right';
export interface RendererOptions {
layerGap?: number;
nodeHeight?: number;
direction?: Direction;
}
type Point = [number, number];
type Waypoints = Point[][];
declare function lineTo(point: Point): string;
declare function moveTo(point: Point): string;
declare function curveTo(c1: Point, c2: Point, point2: Point): string;
declare function vCurveBetween(point1: Point, point2: Point): string;
declare function hCurveBetween(point1: Point, point2: Point): string;
export declare class Renderer {
options: Required<RendererOptions>;
static lineTo: typeof lineTo;
static moveTo: typeof moveTo;
static curveTo: typeof curveTo;
static vCurveBetween: typeof vCurveBetween;
static hCurveBetween: typeof hCurveBetween;
constructor(options?: RendererOptions);
getWaypoints(node: Node): Waypoints;
layout(nodes: Node[]): Node[];
generatePath(node: Node): string;
}
export default Renderer;
11 changes: 11 additions & 0 deletions dist/core/util.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Node } from './labelNode.js';
export interface GenerateNodesOptions {
minWidth?: number;
maxWidth?: number;
minPos?: number;
maxPos?: number;
}
export declare const util: {
generateNodes(amount: number, options?: GenerateNodesOptions): Node[];
};
export default util;
10 changes: 10 additions & 0 deletions dist/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export { Node } from './core/labelNode.js';
export { Force } from './core/force.js';
export type { ForceOptions } from './core/force.js';
export { Distributor } from './core/distributor.js';
export type { DistributorOptions } from './core/distributor.js';
export { Renderer } from './core/renderer.js';
export type { RendererOptions, Direction } from './core/renderer.js';
export { metrics } from './core/metrics.js';
export { util } from './core/util.js';
export type { GenerateNodesOptions } from './core/util.js';
Loading