plotboilerplate
Version:
A simple javascript plotting boilerplate for 2d stuff.
391 lines (361 loc) • 14.5 kB
text/typescript
/**
* A helper to resize bezier paths in horizontal and vertical orientation.
*
* @requires PlotBoilerplate
* @requires BezierPath
* @requires Vertex
*
* @author Ikaros Kappler
* @date 2026-04-04 Copied from the ngdg project, made minor type adaptions.
* @version 1.2.0
*
*/
import { BezierPath } from "../../BezierPath";
import PlotBoilerplate from "../../PlotBoilerplate";
import { Vertex } from "../../Vertex";
import { DrawLib, XYCoords } from "../../interfaces";
// A closure to hide helper functions.
export class BezierResizeHelper {
private pb: PlotBoilerplate;
public readonly bezierPath: BezierPath;
public readonly topResizeHandle: Vertex;
public readonly leftResizeHandle: Vertex;
public readonly bottomResizeHandle: Vertex;
public readonly rightResizeHandle: Vertex;
topHandleDragStartPosition: XYCoords;
leftHandleDragStartPosition: XYCoords;
bottomHandleDragStartPosition: XYCoords;
rightHandleDragStartPosition: XYCoords;
__listeners: Array<() => void>;
/**
* The constructor.
*
* @param {PlotBoilerplate} pb
* @param {BezierPath} bezierPath
* @param {function} updateCallback
*/
constructor(pb: PlotBoilerplate, bezierPath: BezierPath, updateCallback: () => void) {
this.pb = pb;
this.bezierPath = bezierPath;
// @public
this.topResizeHandle = new Vertex(0, 0);
this.leftResizeHandle = new Vertex(0, 0);
this.bottomResizeHandle = new Vertex(0, 0);
this.rightResizeHandle = new Vertex(0, 0);
// @private
this.topHandleDragStartPosition = null;
this.leftHandleDragStartPosition = null;
this.bottomHandleDragStartPosition = null;
this.rightHandleDragStartPosition = null;
this.__listeners = this.__installListeners(this, pb, bezierPath, updateCallback);
this.updateResizeHandles();
}
draw(draw: DrawLib<any>, fill: DrawLib<any>) {
this.drawHandleLines(draw, fill, "grey");
this.drawTriangles(draw, fill, "rgba(0,192,192,1.0)");
}
/**
* Call this method from the outside to see active helper lines for those edges
* that are currently dragged.
*
* @param {DrawLib<any>} draw
* @param {DrawLib<any>} fill
* @param {string} lineColor
*/
drawHandleLines(draw: DrawLib<any>, fill: DrawLib<any>, lineColor: string) {
// console.log("drawResizeHandleLines", bezierResizer.verticalResizeHandleDragStartPosition);
var bounds = this.bezierPath.getBounds();
if (this.topHandleDragStartPosition) {
draw.line({ x: bounds.min.x, y: this.topResizeHandle.y }, { x: bounds.max.x, y: this.topResizeHandle.y }, lineColor, 1.0, {
dashOffset: 0.0,
dashArray: [5, 5]
});
}
if (this.leftHandleDragStartPosition) {
draw.line(
{ x: this.leftResizeHandle.x, y: bounds.min.y },
{ x: this.leftResizeHandle.x, y: bounds.max.y },
lineColor,
1.0,
{ dashOffset: 0.0, dashArray: [5, 5] }
);
}
if (this.bottomHandleDragStartPosition) {
draw.line(
{ x: bounds.min.x, y: this.bottomResizeHandle.y },
{ x: bounds.max.x, y: this.bottomResizeHandle.y },
lineColor,
1.0,
{
dashOffset: 0.0,
dashArray: [5, 5]
}
);
}
if (this.rightHandleDragStartPosition) {
draw.line(
{ x: this.rightResizeHandle.x, y: bounds.min.y },
{ x: this.rightResizeHandle.x, y: bounds.max.y },
lineColor,
1.0,
{ dashOffset: 0.0, dashArray: [5, 5] }
);
}
}
public drawTriangles(draw: DrawLib<any>, _fill: DrawLib<any>, color: string) {
// this.__drawUpTriangle(draw, this.topResizeHandle, color);
// this.__drawDownTriangle(draw, this.bottomResizeHandle, color);
// this.__drawLeftTriangle(draw, this.leftResizeHandle, color);
// this.__drawRightTriangle(draw, this.rightResizeHandle, color);
this.drawTopTriangle(draw, _fill, color);
this.drawLeftTriangle(draw, _fill, color);
this.drawBottomTriangle(draw, _fill, color);
this.drawRightTriangle(draw, _fill, color);
}
/**
* Draws an up-trinangle at the top handle position.
*
* @param draw
* @param position
* @param color
*/
drawTopTriangle(draw: DrawLib<any>, _fill: DrawLib<any>, color: string) {
this.__drawUpTriangle(draw, _fill, this.topResizeHandle, color);
}
/**
* Draws an up-trinangle at the top handle position.
*
* @param draw
* @param position
* @param color
*/
drawLeftTriangle(draw: DrawLib<any>, _fill: DrawLib<any>, color: string) {
this.__drawLeftTriangle(draw, _fill, this.leftResizeHandle, color);
}
/**
* Draws an up-trinangle at the top handle position.
*
* @param draw
* @param position
* @param color
*/
drawBottomTriangle(draw: DrawLib<any>, _fill: DrawLib<any>, color: string) {
this.__drawDownTriangle(draw, _fill, this.bottomResizeHandle, color);
}
/**
* Draws an right-trinangle at the top handle position.
*
* @param draw
* @param position
* @param color
*/
drawRightTriangle(draw: DrawLib<any>, _fill: DrawLib<any>, color: string) {
this.__drawRightTriangle(draw, _fill, this.rightResizeHandle, color);
}
/**
* Draws an up-trinangle at the top handle position.
*
* @param draw
* @param position
* @param color
*/
private __drawUpTriangle(draw: DrawLib<any>, _fill: DrawLib<any>, position: XYCoords, color: string) {
const arrowSize = 6.0;
const arrowSizeH = arrowSize / this.pb.config.scaleX;
const arrowSizeV = arrowSize / this.pb.config.scaleY;
const verts: XYCoords[] = [
{ x: position.x - arrowSizeH, y: position.y + arrowSizeV * 0.5 }, // left corner
{ x: position.x, y: position.y - arrowSizeV }, // up corner
{ x: position.x + arrowSizeH, y: position.y + arrowSizeV * 0.5 } // right corner
];
draw.polyline(verts, false, color, 1.0);
}
/**
* Draws an left-trinangle at the left handle position.
*
* @param draw
* @param position
* @param color
*/
private __drawLeftTriangle(draw: DrawLib<any>, _fill: DrawLib<any>, position: XYCoords, color: string) {
const arrowSize = 6.0;
const arrowSizeH = arrowSize / this.pb.config.scaleX;
const arrowSizeV = arrowSize / this.pb.config.scaleY;
const verts: XYCoords[] = [
{ x: position.x - arrowSizeH, y: position.y }, // left corner
{ x: position.x + arrowSizeH * 0.5, y: position.y - arrowSizeV }, // up corner
{ x: position.x + arrowSizeH * 0.5, y: position.y + arrowSizeV } // down corner
];
draw.polyline(verts, false, color, 1.0);
}
/**
* Draws a down-trinangle at the top handle position.
*
* @param draw
* @param position
* @param color
*/
private __drawDownTriangle(draw: DrawLib<any>, _fill: DrawLib<any>, position: XYCoords, color: string) {
const arrowSize = 6.0;
const arrowSizeH = arrowSize / this.pb.config.scaleX;
const arrowSizeV = arrowSize / this.pb.config.scaleY;
const verts: XYCoords[] = [
{ x: position.x + arrowSizeH, y: position.y - arrowSizeV * 0.5 }, // right corner
{ x: position.x, y: position.y + arrowSizeV }, // down corner
{ x: position.x - arrowSizeH, y: position.y - arrowSizeV * 0.5 } // left corner
];
draw.polyline(verts, false, color, 1.0);
}
/**
* Draws an left-trinangle at the left handle position.
*
* @param draw
* @param position
* @param color
*/
private __drawRightTriangle(draw: DrawLib<any>, _fill: DrawLib<any>, position: XYCoords, color: string) {
const arrowSize = 6.0;
const arrowSizeH = arrowSize / this.pb.config.scaleX;
const arrowSizeV = arrowSize / this.pb.config.scaleY;
const verts: XYCoords[] = [
{ x: position.x + arrowSizeH, y: position.y }, // left corner
{ x: position.x - arrowSizeH * 0.5, y: position.y - arrowSizeV }, // up corner
{ x: position.x - arrowSizeH * 0.5, y: position.y + arrowSizeV } // down corner
];
draw.polyline(verts, false, color, 1.0);
}
/**
* Destroys this helper by removing all previously installed vertex listeners.
*/
destroy() {
this.topResizeHandle.listeners.removeDragStartListener(this.__listeners[0]);
this.topResizeHandle.listeners.removeDragEndListener(this.__listeners[1]);
this.leftResizeHandle.listeners.removeDragStartListener(this.__listeners[2]);
this.leftResizeHandle.listeners.removeDragEndListener(this.__listeners[3]);
this.bottomResizeHandle.listeners.removeDragStartListener(this.__listeners[4]);
this.bottomResizeHandle.listeners.removeDragEndListener(this.__listeners[5]);
this.rightResizeHandle.listeners.removeDragStartListener(this.__listeners[6]);
this.rightResizeHandle.listeners.removeDragEndListener(this.__listeners[7]);
}
/**
* Install all required drag-start and drag-end listeners.
*/
private __installListeners(_self: BezierResizeHelper, pb: PlotBoilerplate, bezierPath: BezierPath, updateCallback: () => void) {
var listeners = [];
listeners.push(
_self.topResizeHandle.listeners.addDragStartListener(function (e) {
var relPos = pb.transformMousePosition(e.params.draggedFrom.x, e.params.draggedFrom.y);
_self.topHandleDragStartPosition = relPos;
})
);
listeners.push(
_self.topResizeHandle.listeners.addDragEndListener(function (e) {
var relTargetPos = pb.transformMousePosition(e.params.pos.x, e.params.pos.y);
var targetHeightDifference = _self.topHandleDragStartPosition.y - relTargetPos.y;
_self.__changePathHeightTop(bezierPath, targetHeightDifference);
_self.updateResizeHandles();
_self.topHandleDragStartPosition = null;
updateCallback();
})
);
listeners.push(
_self.leftResizeHandle.listeners.addDragStartListener(function (e) {
var relPos = pb.transformMousePosition(e.params.draggedFrom.x, e.params.draggedFrom.y);
_self.leftHandleDragStartPosition = relPos;
})
);
listeners.push(
_self.leftResizeHandle.listeners.addDragEndListener(function (e) {
var relTargetPos = pb.transformMousePosition(e.params.pos.x, e.params.pos.y);
var targetWidthDifference = _self.leftHandleDragStartPosition.x - relTargetPos.x;
_self.__changePathWidthLeft(bezierPath, targetWidthDifference);
_self.updateResizeHandles();
_self.leftHandleDragStartPosition = null;
updateCallback();
})
);
listeners.push(
_self.bottomResizeHandle.listeners.addDragStartListener(function (e) {
var relPos = pb.transformMousePosition(e.params.draggedFrom.x, e.params.draggedFrom.y);
_self.bottomHandleDragStartPosition = relPos;
})
);
listeners.push(
_self.bottomResizeHandle.listeners.addDragEndListener(function (e) {
var relTargetPos = pb.transformMousePosition(e.params.pos.x, e.params.pos.y);
var targetHeightDifference = relTargetPos.y - _self.bottomHandleDragStartPosition.y;
_self.__changePathHeightBottom(bezierPath, targetHeightDifference);
_self.updateResizeHandles();
_self.bottomHandleDragStartPosition = null;
updateCallback();
})
);
listeners.push(
_self.rightResizeHandle.listeners.addDragStartListener(function (e) {
var relPos = pb.transformMousePosition(e.params.draggedFrom.x, e.params.draggedFrom.y);
_self.rightHandleDragStartPosition = relPos;
})
);
listeners.push(
_self.rightResizeHandle.listeners.addDragEndListener(function (e) {
var relTargetPos = pb.transformMousePosition(e.params.pos.x, e.params.pos.y);
var targetWidthDifference = relTargetPos.x - _self.rightHandleDragStartPosition.x;
_self.__changePathWidthRight(bezierPath, targetWidthDifference);
_self.updateResizeHandles();
_self.rightHandleDragStartPosition = null;
updateCallback();
})
);
return listeners;
}
/**
* @param {BezierPath} bezierPath - The path to update.
* @param {number} heightAmount - The height difference to apply. Can be positive or negative or zero.
*/
private __changePathHeightTop(bezierPath: BezierPath, heightAmount: number) {
const bounds = bezierPath.getBounds();
const scaleAnchor = bounds.max;
const verticalScaleFactor = (bounds.height + heightAmount) / bounds.height;
bezierPath.scaleXY({ x: 1.0, y: verticalScaleFactor }, scaleAnchor);
}
/**
* @param {BezierPath} bezierPath - The path to update.
* @param {number} widthAmount - The width difference to apply. Can be positive or negative or zero.
*/
private __changePathWidthLeft(bezierPath: BezierPath, widthAmount: number) {
const bounds = bezierPath.getBounds();
const scaleAnchor = bounds.max;
const horizontalScaleFactor = (bounds.width + widthAmount) / bounds.width;
bezierPath.scaleXY({ x: horizontalScaleFactor, y: 1.0 }, scaleAnchor);
}
/**
* @param {BezierPath} bezierPath - The path to update.
* @param {number} heightAmount - The height difference to apply. Can be positive or negative or zero.
*/
private __changePathHeightBottom(bezierPath: BezierPath, heightAmount: number) {
const bounds = bezierPath.getBounds();
const scaleAnchor = bounds.min;
const verticalScaleFactor = (bounds.height + heightAmount) / bounds.height;
bezierPath.scaleXY({ x: 1.0, y: verticalScaleFactor }, scaleAnchor);
}
/**
* @param {BezierPath} bezierPath - The path to update.
* @param {number} widthAmount - The width difference to apply. Can be positive or negative or zero.
*/
private __changePathWidthRight(bezierPath: BezierPath, widthAmount: number) {
const bounds = bezierPath.getBounds();
const scaleAnchor = bounds.min;
const horizontalScaleFactor = (bounds.width + widthAmount) / bounds.width;
bezierPath.scaleXY({ x: horizontalScaleFactor, y: 1.0 }, scaleAnchor);
}
/**
* Set the handles to the new position after the path was resized.
*/
public updateResizeHandles() {
const bounds = this.bezierPath.getBounds();
this.leftResizeHandle.set(bounds.min.x, bounds.min.y + bounds.height / 2.0);
this.topResizeHandle.set(bounds.min.x + bounds.width / 2.0, bounds.min.y);
this.rightResizeHandle.set(bounds.max.x, bounds.min.y + bounds.height / 2.0);
this.bottomResizeHandle.set(bounds.min.x + bounds.width / 2.0, bounds.max.y);
}
} // END class