@plait/graph-viz
Version:
- `@plait/core`
750 lines (729 loc) • 28.4 kB
JavaScript
import { cacheSelectedElements, getPointBetween, PlaitBoard, normalizePoint, drawCircle, createForeignObject, createG, createPath, PlaitElement, PlaitNode, getSelectedElements, RectangleClient, setSelectionOptions, PlaitPluginKey, toViewBoxPoint, toHostPoint, isHitElement, PlaitPointerType } from '@plait/core';
import { CommonElementFlavour, Generator, animate, linear } from '@plait/common';
import Graph from 'graphology';
import circular from 'graphology-layout/circular';
import forceAtlas2 from 'graphology-layout-forceatlas2';
const DEFAULT_STYLES = {
fillStyle: 'solid',
strokeWidth: 1
};
var EdgeDirection;
(function (EdgeDirection) {
EdgeDirection[EdgeDirection["IN"] = 0] = "IN";
EdgeDirection[EdgeDirection["OUT"] = 1] = "OUT";
EdgeDirection[EdgeDirection["NONE"] = 2] = "NONE";
})(EdgeDirection || (EdgeDirection = {}));
const DEFAULT_EDGE_STYLES = {
...DEFAULT_STYLES,
stroke: '#ddd'
};
const DEFAULT_NODE_SIZE = 30;
const DEFAULT_ACTIVE_NODE_SIZE_MULTIPLIER = 1.2;
const DEFAULT_ACTIVE_WAVE_NODE_SIZE_MULTIPLIER = 1.5;
const DEFAULT_NODE_LABEL_MARGIN_TOP = 4;
const DEFAULT_NODE_LABEL_FONT_SIZE = 12;
const DEFAULT_NODE_LABEL_WIDTH = 72;
const DEFAULT_NODE_LABEL_HEIGHT = 22;
const DEFAULT_NODE_LABEL_STYLE = `user-select:none;max-width:${DEFAULT_NODE_LABEL_WIDTH}px;text-align:center;line-height:${DEFAULT_NODE_LABEL_HEIGHT}px;font-size:${DEFAULT_NODE_LABEL_FONT_SIZE}px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;`;
const NODE_LABEL_CLASS_NAME = 'force-atlas-node-label';
const SECOND_DEPTH_NODE_ALPHA = 0.5;
const SECOND_DEPTH_LINE_ALPHA = 0.5;
const ACTIVE_BACKGROUND_NODE_ALPHA = 0.1;
const NODE_ICON_CLASS_NAME = 'force-atlas-node-icon';
const ACTIVE_NODE_ICON_CLASS_NAME = 'force-atlas-node-icon-active';
const NODE_ICON_FONT_SIZE = 16;
const ACTIVE_NODE_ICON_FONT_SIZE = 18;
const DEFAULT_NODE_BACKGROUND_COLOR = '#9c9cfb';
const DEFAULT_NODE_ICON_COLOR = '#fff';
const DEFAULT_NODE_STYLES = {
...DEFAULT_STYLES,
fill: DEFAULT_NODE_BACKGROUND_COLOR,
strokeWidth: 0
};
const DEFAULT_NODE_SCALING_RATIO = 20;
const DEFAULT_LINE_STYLES = {
color: {
[EdgeDirection.IN]: '#73D897',
[EdgeDirection.OUT]: '#6698FF',
[EdgeDirection.NONE]: `#eee`
},
opacity: {
[EdgeDirection.IN]: 0.4,
[EdgeDirection.OUT]: 0.4,
[EdgeDirection.NONE]: 1
}
};
const DEFAULT_EDGE_PARTICLE_SIZE = 4;
const ForceAtlasElement = {
isForceAtlas: (value) => {
return value?.type === 'force-atlas';
},
isForceAtlasNodeElement: (value) => {
return value && value.label && value.icon;
},
isForceAtlasEdgeElement: (value) => {
return value && value.source && value.target;
}
};
class ForceAtlasFlavour extends CommonElementFlavour {
constructor() {
super();
}
initializeGraph() {
this.graph = new Graph();
this.element.children?.forEach((child) => {
if (ForceAtlasElement.isForceAtlasNodeElement(child)) {
if (typeof child?.size === 'undefined') {
child.size = DEFAULT_NODE_SIZE;
}
if (child.isActive) {
cacheSelectedElements(this.board, [child]);
}
this.graph.addNode(child.id, child);
}
else if (ForceAtlasElement.isForceAtlasEdgeElement(child)) {
this.graph.addEdge(child.source, child.target);
}
});
circular.assign(this.graph);
const nodeCount = this.graph.nodes().length;
const settings = forceAtlas2.inferSettings(this.graph);
settings.scalingRatio = 450;
if (nodeCount > 5) {
settings.gravity = 0.2;
settings.adjustSizes = false;
}
else {
settings.gravity = 0;
settings.adjustSizes = true;
}
const positions = forceAtlas2(this.graph, { iterations: 500, settings });
this.element.children?.forEach((child) => {
if (ForceAtlasElement.isForceAtlasNodeElement(child)) {
const pos = positions[child.id];
child.points = [[pos.x, pos.y]];
}
});
}
initialize() {
super.initialize();
this.initializeGraph();
}
onContextChanged(value, previous) { }
updateText(previousElement, currentElement) { }
destroy() {
super.destroy();
}
}
// Credits to perfect-arrows
// https://github.com/steveruizok/perfect-arrows/blob/master/src/lib/utils.ts
const PI = Math.PI;
/**
* Modulate a value between two ranges.
* @param value
* @param rangeA from [low, high]
* @param rangeB to [low, high]
* @param clamp
*/
function modulate(value, rangeA, rangeB, clamp = false) {
const [fromLow, fromHigh] = rangeA;
const [toLow, toHigh] = rangeB;
const result = toLow + ((value - fromLow) / (fromHigh - fromLow)) * (toHigh - toLow);
if (clamp === true) {
if (toLow < toHigh) {
if (result < toLow) {
return toLow;
}
if (result > toHigh) {
return toHigh;
}
}
else {
if (result > toLow) {
return toLow;
}
if (result < toHigh) {
return toHigh;
}
}
}
return result;
}
/**
* Rotate a point around a center.
* @param x The x-axis coordinate of the point.
* @param y The y-axis coordinate of the point.
* @param cx The x-axis coordinate of the point to rotate round.
* @param cy The y-axis coordinate of the point to rotate round.
* @param angle The distance (in radians) to rotate.
*/
function rotatePoint(x, y, cx, cy, angle) {
const s = Math.sin(angle);
const c = Math.cos(angle);
const px = x - cx;
const py = y - cy;
const nx = px * c - py * s;
const ny = px * s + py * c;
return [nx + cx, ny + cy];
}
/**
* Get the distance between two points.
* @param x0 The x-axis coordinate of the first point.
* @param y0 The y-axis coordinate of the first point.
* @param x1 The x-axis coordinate of the second point.
* @param y1 The y-axis coordinate of the second point.
*/
function getDistance(x0, y0, x1, y1) {
return Math.hypot(y1 - y0, x1 - x0);
}
/**
* Get an angle (radians) between two points.
* @param x0 The x-axis coordinate of the first point.
* @param y0 The y-axis coordinate of the first point.
* @param x1 The x-axis coordinate of the second point.
* @param y1 The y-axis coordinate of the second point.
*/
function getAngle(x0, y0, x1, y1) {
return Math.atan2(y1 - y0, x1 - x0);
}
/**
* Move a point in an angle by a distance.
* @param x0
* @param y0
* @param a angle (radians)
* @param d distance
*/
function projectPoint(x0, y0, a, d) {
return [Math.cos(a) * d + x0, Math.sin(a) * d + y0];
}
/**
* Get the sector of an angle (e.g. quadrant, octant)
* @param a The angle to check.
* @param s The number of sectors to check.
*/
function getSector(a, s = 8) {
return Math.floor(s * (0.5 + ((a / (PI * 2)) % s)));
}
/**
* Get a normal value representing how close two points are from being at a 45 degree angle.
* @param x0 The x-axis coordinate of the first point.
* @param y0 The y-axis coordinate of the first point.
* @param x1 The x-axis coordinate of the second point.
* @param y1 The y-axis coordinate of the second point.
*/
function getAngliness(x0, y0, x1, y1) {
return Math.abs((x1 - x0) / 2 / ((y1 - y0) / 2));
}
// Credits to perfect-arrows
// https://github.com/steveruizok/perfect-arrows/blob/master/src/lib/getArrow.ts
/**
* getArrow
* Get the points for a linking line between two points.
* @description Draw an arrow between two points.
* @param x0 The x position of the "from" point.
* @param y0 The y position of the "from" point.
* @param x1 The x position of the "to" point.
* @param y1 The y position of the "to" point.
* @param options Additional options for computing the line.
* @returns [sx, sy, cx, cy, e1, e2, ae, as, ac]
* @example
* const arrow = getArrow(0, 0, 100, 200, {
bow: 0
stretch: .5
stretchMin: 0
stretchMax: 420
padStart: 0
padEnd: 0
flip: false
straights: true
* })
*
* const [
* startX, startY,
* controlX, controlY,
* endX, endY,
* endAngle,
* startAngle,
* controlAngle
* ] = arrow
*/
function getArrow(x0, y0, x1, y1, options = {}) {
const { bow = 0, stretch = 0.5, stretchMin = 0, stretchMax = 420, padStart = 0, padEnd = 0, flip = false, straights = true } = options;
const angle = getAngle(x0, y0, x1, y1);
const dist = getDistance(x0, y0, x1, y1);
const angliness = getAngliness(x0, y0, x1, y1);
// Step 0 ⤜⤏ Should the arrow be straight?
if (dist < (padStart + padEnd) * 2 || // Too short
(bow === 0 && stretch === 0) || // No bow, no stretch
(straights && [0, 1, Infinity].includes(angliness)) // 45 degree angle
) {
// ⤜⤏ Arrow is straight! Just pad start and end points.
// Padding distances
const ps = Math.max(0, Math.min(dist - padStart, padStart));
const pe = Math.max(0, Math.min(dist - ps, padEnd));
// Move start point toward end point
let [px0, py0] = projectPoint(x0, y0, angle, ps);
// Move end point toward start point
let [px1, py1] = projectPoint(x1, y1, angle + Math.PI, pe);
// Get midpoint between new points
const [mx, my] = getPointBetween(px0, py0, px1, py1, 0.5);
return [px0, py0, mx, my, px1, py1, angle, angle, angle];
}
// ⤜⤏ Arrow is an arc!
// Is the arc clockwise or counterclockwise?
let rot = (getSector(angle) % 2 === 0 ? 1 : -1) * (flip ? -1 : 1);
// Calculate how much the line should "bow" away from center
const arc = bow + modulate(dist, [stretchMin, stretchMax], [1, 0], true) * stretch;
// Step 1 ⤜⤏ Find padded points.
// Get midpoint.
const [mx, my] = getPointBetween(x0, y0, x1, y1, 0.5);
// Get control point.
let [cx, cy] = getPointBetween(x0, y0, x1, y1, 0.5 - arc);
// Rotate control point (clockwise or counterclockwise).
[cx, cy] = rotatePoint(cx, cy, mx, my, (Math.PI / 2) * rot);
// Get padded start point.
const a0 = getAngle(x0, y0, cx, cy);
const [px0, py0] = projectPoint(x0, y0, a0, padStart);
// Get padded end point.
const a1 = getAngle(x1, y1, cx, cy);
const [px1, py1] = projectPoint(x1, y1, a1, padEnd);
// Step 2 ⤜⤏ Find start and end angles.
// Start angle
const as = getAngle(cx, cy, x0, y0);
// End angle
const ae = getAngle(cx, cy, x1, y1);
// Step 3 ⤜⤏ Find control point for padded points.
// Get midpoint between padded start / end points.
const [mx1, my1] = getPointBetween(px0, py0, px1, py1, 0.5);
// Get control point for padded start / end points.
let [cx1, cy1] = getPointBetween(px0, py0, px1, py1, 0.5 - arc);
// Rotate control point (clockwise or counterclockwise).
[cx1, cy1] = rotatePoint(cx1, cy1, mx1, my1, (Math.PI / 2) * rot);
// Finally, average the two control points.
let [cx2, cy2] = getPointBetween(cx, cy, cx1, cy1, 0.5);
return [px0, py0, cx2, cy2, px1, py1, ae, as, angle];
}
function drawNode(board, node, point, options) {
const roughSVG = PlaitBoard.getRoughSVG(board);
const nodeStyles = {
...DEFAULT_NODE_STYLES,
...(node.styles || {})
};
let { x, y } = normalizePoint(point);
let diameter = node.size ?? DEFAULT_NODE_SIZE;
if (options.isActive) {
diameter = diameter * DEFAULT_ACTIVE_NODE_SIZE_MULTIPLIER;
}
const nodeG = drawCircle(roughSVG, [x, y], diameter, nodeStyles);
const labelWidth = node.styles?.labelWidth ?? DEFAULT_NODE_LABEL_WIDTH;
const labelHeight = node.styles?.labelHeight ?? DEFAULT_NODE_LABEL_HEIGHT;
const textForeignObject = createForeignObject(x - labelWidth / 2, y, labelWidth, labelHeight);
const textContainer = document.createElement('div');
textContainer.classList.add(NODE_LABEL_CLASS_NAME);
textContainer.setAttribute('style', DEFAULT_NODE_LABEL_STYLE);
const text = document.createElement('span');
text.innerText = node.label;
textContainer.append(text);
textForeignObject.append(textContainer);
if (options.isActive) {
const waveDiameter = diameter * DEFAULT_ACTIVE_WAVE_NODE_SIZE_MULTIPLIER;
const waveCircle = drawCircle(roughSVG, [x, y], waveDiameter, nodeStyles);
waveCircle.setAttribute('opacity', ACTIVE_BACKGROUND_NODE_ALPHA.toString());
nodeG.append(waveCircle);
textForeignObject.setAttribute('y', `${y + waveDiameter / 2}`);
}
else {
textForeignObject.setAttribute('y', `${y + diameter / 2}`);
nodeG.setAttribute('opacity', (options.opacity ?? 1).toString());
}
if (options.iconG) {
nodeG.append(options.iconG);
}
nodeG.append(textForeignObject);
return nodeG;
}
function drawEdge(startPoint, endPoint, direction, isMutual, isTargetSelf) {
const nodeRadius = DEFAULT_NODE_SIZE / 2;
const arrow = getArrow(startPoint[0], startPoint[1], endPoint[0], endPoint[1], {
stretch: 0.4,
flip: direction === EdgeDirection.NONE ? false : isMutual,
padEnd: nodeRadius,
padStart: nodeRadius
});
const [sx, sy, cx, cy, ex, ey, ae, as, ec] = arrow;
const g = createG();
const path = createPath();
if (!isTargetSelf) {
path.setAttribute('d', `M${sx},${sy} Q${cx},${cy} ${ex},${ey}`);
}
else {
const x = startPoint[0];
const y = startPoint[1];
const besselX = 40;
const besselY = 75;
const angle = 55;
const angleRad = (angle * Math.PI) / 180;
const offsetX = nodeRadius * Math.cos(angleRad);
const offsetY = nodeRadius * Math.sin(angleRad);
path.setAttribute('d', `M ${x - offsetX},${y - offsetY}
C ${x - besselX},${y - besselY}, ${x + besselX} ${y - besselY}
${x + offsetX},${y - offsetY}`);
}
path.setAttribute('fill', 'none');
path.setAttribute('stroke', DEFAULT_LINE_STYLES.color[direction]);
path.setAttribute('opacity', DEFAULT_LINE_STYLES.opacity[direction].toString());
g.append(path);
return {
g,
path
};
}
function drawParticle(board, startPoint, direction) {
const roughSVG = PlaitBoard.getRoughSVG(board);
const pointG = drawCircle(roughSVG, [0, 0], DEFAULT_EDGE_PARTICLE_SIZE, {
...DEFAULT_STYLES,
strokeWidth: 0,
fill: DEFAULT_LINE_STYLES.color[direction]
});
pointG.setAttribute('transform', `translate(${startPoint[0]}, ${startPoint[1]})`);
return pointG;
}
class ForceAtlasEdgeGenerator extends Generator {
static { this.key = 'force-atlas-edge'; }
constructor(board) {
super(board);
}
canDraw(element) {
return true;
}
draw(element, data) {
const edgeG = createG();
const edgeElement = drawEdge(data.startPoint, data.endPoint, data.direction, data.isSourceActive && data.isTargetActive, data.isTargetSelf);
edgeG.append(edgeElement.g);
if (data.direction !== EdgeDirection.NONE) {
const particle = drawParticle(this.board, data.startPoint, data.direction);
edgeElement.g.append(particle);
this.particleAnimation = playEdgeParticleAnimate(edgeElement.path, particle);
}
return edgeG;
}
destroy() {
super.destroy();
this.particleAnimation?.stop();
}
}
function getEdges(forceAtlasElement, andCallBack) {
return forceAtlasElement.children?.filter((f) => ForceAtlasElement.isForceAtlasEdgeElement(f) && (andCallBack?.(f) ?? true));
}
function getEdgeById(id, forceAtlasElement) {
const edge = getEdges(forceAtlasElement, (e) => e.id === id)?.[0];
if (!edge) {
throw new Error('can not find edge.');
}
return edge;
}
function getEdgesInSourceOrTarget(id, forceAtlasElement) {
const edges = getEdges(forceAtlasElement, (edge) => edge.source === id || edge.target === id);
return edges;
}
function getEdgeGenerator(edge) {
const edgeRef = PlaitElement.getElementRef(edge);
return edgeRef.getGenerator(ForceAtlasEdgeGenerator.key);
}
function getEdgeDirection(isSourceActive, isTargetActive) {
if (isSourceActive) {
return EdgeDirection.OUT;
}
else if (isTargetActive) {
return EdgeDirection.IN;
}
return EdgeDirection.NONE;
}
function getEdgeGeneratorData(edge, board) {
const forceAtlasElement = PlaitNode.parent(board, PlaitBoard.findPath(board, edge));
const sourceNode = getNodeById(edge.source, forceAtlasElement);
const targetNode = getNodeById(edge.target, forceAtlasElement);
if (!sourceNode?.points || !targetNode?.points) {
throw new Error("Source or target node doesn't have points");
}
const startPoint = sourceNode.points[0];
const endPoint = targetNode.points[0];
const selectElements = getSelectedElements(board);
const isSourceActive = getIsNodeActive(sourceNode.id, selectElements);
const isTargetActive = getIsNodeActive(targetNode.id, selectElements);
const direction = getEdgeDirection(isSourceActive, isTargetActive);
return {
startPoint,
endPoint,
direction,
isSourceActive,
isTargetActive,
isTargetSelf: sourceNode.id === targetNode.id
};
}
function playEdgeParticleAnimate(path, pointG) {
const pathLength = path.getTotalLength();
let anim = animate((t) => {
const point = path.getPointAtLength(t * pathLength);
pointG.setAttribute('transform', `translate(${point.x}, ${point.y})`);
}, 1000, linear, () => {
anim = playEdgeParticleAnimate(path, pointG);
});
return {
stop: () => {
anim.stop();
},
start: () => {
anim.start();
}
};
}
function getNodes(forceAtlasElement, andBack) {
return forceAtlasElement.children?.filter((f) => ForceAtlasElement.isForceAtlasNodeElement(f) && (andBack?.(f) ?? true));
}
function getNodeById(id, forceAtlasElement) {
const node = getNodes(forceAtlasElement, (node) => node.id === id)?.[0];
if (!node) {
throw new Error('can not find node.');
}
return node;
}
function getIsNodeActive(id, selectElements) {
return selectElements.some((node) => node.id === id);
}
function isHitNode(node, point) {
const { x, y } = normalizePoint(node.points[0]);
const size = node.size;
const hitFlowNode = RectangleClient.isHit(RectangleClient.getRectangleByPoints(point), {
x: x - size / 2,
y: y - size / 2,
width: size,
height: size
});
return hitFlowNode;
}
function getAssociatedNodesById(id, forceAtlasElement) {
const edges = getEdgesInSourceOrTarget(id, forceAtlasElement);
const nodes = [];
edges.forEach((edge) => {
nodes.push(getNodeById(edge.source, forceAtlasElement));
nodes.push(getNodeById(edge.target, forceAtlasElement));
});
return nodes;
}
function getNodeGenerator(node) {
const edgeRef = PlaitElement.getElementRef(node);
return edgeRef.getGenerator(ForceAtlasNodeGenerator.key);
}
function isFirstDepthNode(currentNodeId, activeNodeId, forceAtlasElement) {
const edges = getEdges(forceAtlasElement);
return edges.some((s) => (s.source === activeNodeId && s.target === currentNodeId) || (s.target === activeNodeId && s.source === currentNodeId));
}
function getNodeIcon(node) {
const iconItem = typeof node.icon === 'object' && node.icon.name ? node.icon : null;
return {
name: iconItem ? iconItem.name : node.icon,
fontSize: (iconItem && iconItem.fontSize) || NODE_ICON_FONT_SIZE,
color: (iconItem && iconItem.color) || DEFAULT_NODE_ICON_COLOR
};
}
class ForceAtlasNodeGenerator extends Generator {
static { this.key = 'force-atlas-node'; }
constructor(board) {
super(board);
}
canDraw(element) {
return true;
}
draw(element, data) {
const iconRef = this.drawIcon(element, data);
return drawNode(this.board, element, element?.points?.[0] || [0, 0], { ...data, iconG: iconRef.iconG });
}
drawIcon(element, data) {
const iconG = createG();
let { x, y } = normalizePoint(element.points?.[0] || [0, 0]);
const size = element.size;
const foreignObject = createForeignObject(x - size / 2, y - size / 2, size, size);
iconG.append(foreignObject);
const container = document.createElement('div');
container.classList.add(NODE_ICON_CLASS_NAME);
if (data.isActive) {
container.classList.add(ACTIVE_NODE_ICON_CLASS_NAME);
}
foreignObject.append(container);
const nodeIcon = getNodeIcon(element);
const props = {
iconItem: {
name: nodeIcon.name,
fontSize: data.isActive ? ACTIVE_NODE_ICON_FONT_SIZE : nodeIcon.fontSize,
color: nodeIcon.color
},
board: this.board,
element: element
};
const ref = this.board.renderNodeIcon(container, props);
return { ref, iconG };
}
}
class ForceAtlasNodeFlavour extends CommonElementFlavour {
constructor() {
super();
}
initializeGenerator() {
this.nodeGenerator = new ForceAtlasNodeGenerator(this.board);
this.getRef().addGenerator(ForceAtlasNodeGenerator.key, this.nodeGenerator);
}
initialize() {
super.initialize();
this.initializeGenerator();
const parent = PlaitNode.parent(this.board, PlaitBoard.findPath(this.board, this.element));
const selectElements = getSelectedElements(this.board);
const activeNodeId = selectElements[0]?.id;
const isActive = activeNodeId === this.element.id;
this.nodeGenerator.processDrawing(this.element, this.getElementG(), {
isActive,
opacity: isFirstDepthNode(this.element.id, activeNodeId, parent) ? 1 : SECOND_DEPTH_NODE_ALPHA
});
}
onContextChanged(value, previous) {
if (value !== previous && value.selected !== previous.selected) {
const parent = value.parent;
if (value.selected) {
cacheSelectedElements(this.board, [value.element]);
}
const selectElements = getSelectedElements(this.board);
const nodes = getNodes(parent);
nodes.forEach((node) => {
const nodeGenerator = getNodeGenerator(node);
nodeGenerator.destroy();
const isFirstDepth = selectElements.length > 0 && isFirstDepthNode(node.id, selectElements[0].id, parent);
nodeGenerator.processDrawing(node, this.getElementG(), {
isActive: selectElements?.[0]?.id === node.id,
opacity: selectElements.length === 0 ? 1 : isFirstDepth ? 1 : SECOND_DEPTH_NODE_ALPHA
});
});
const associatedEdges = getEdgesInSourceOrTarget(value.element.id, parent);
associatedEdges.forEach((edge) => {
const edgeGenerator = getEdgeGenerator(edge);
edgeGenerator.destroy();
edgeGenerator.processDrawing(edge, PlaitBoard.getElementLowerHost(this.board), getEdgeGeneratorData(edge, this.board));
});
}
}
updateText(previousElement, currentElement) { }
destroy() {
super.destroy();
}
}
class ForceAtlasEdgeFlavour extends CommonElementFlavour {
constructor() {
super();
}
initializeGenerator() {
this.edgeGenerator = new ForceAtlasEdgeGenerator(this.board);
this.getRef().addGenerator(ForceAtlasEdgeGenerator.key, this.edgeGenerator);
}
initialize() {
super.initialize();
this.initializeGenerator();
this.edgeGenerator.processDrawing(this.element, PlaitBoard.getElementLowerHost(this.board), getEdgeGeneratorData(this.element, this.board));
}
onContextChanged(value, previous) { }
updateText(previousElement, currentElement) { }
destroy() {
super.destroy();
this.edgeGenerator.destroy();
}
}
const withNodeIcon = (board) => {
const newBoard = board;
newBoard.renderNodeIcon = (container, props) => {
throw new Error('No implementation for renderLabeIcon method.');
};
return newBoard;
};
const withForceAtlas = (board) => {
const { drawElement, getRectangle, isRectangleHit, isHit, isInsidePoint, isMovable, isAlign, getRelatedFragment } = board;
board.drawElement = (context) => {
if (ForceAtlasElement.isForceAtlas(context.element)) {
return ForceAtlasFlavour;
}
else if (ForceAtlasElement.isForceAtlasNodeElement(context.element)) {
return ForceAtlasNodeFlavour;
}
else if (ForceAtlasElement.isForceAtlasEdgeElement(context.element)) {
return ForceAtlasEdgeFlavour;
}
return drawElement(context);
};
board.getRectangle = (element) => {
if (element.type === 'force-atlas') {
return {
width: 0,
height: 0,
x: 0,
y: 0
};
}
else if (ForceAtlasElement.isForceAtlasNodeElement(element)) {
return RectangleClient.getRectangleByPoints(element.points || []);
}
else if (ForceAtlasElement.isForceAtlasEdgeElement(element)) {
return {
width: 0,
height: 0,
x: 0,
y: 0
};
}
return getRectangle(element);
};
board.isRectangleHit = (element, selection) => {
return isRectangleHit(element, selection);
};
board.isRectangleHit = (element, range) => {
if (ForceAtlasElement.isForceAtlasNodeElement(element)) {
return isHitNode(element, [range.anchor, range.focus]);
}
return isRectangleHit(element, range);
};
board.isHit = (element, point) => {
if (ForceAtlasElement.isForceAtlasNodeElement(element)) {
return isHitNode(element, [point, point]);
}
return isHit(element, point);
};
board.isInsidePoint = (element, point) => {
return isInsidePoint(element, point);
};
setSelectionOptions(board, { isMultipleSelection: false, isPreventClearSelection: true });
board.setPluginOptions(PlaitPluginKey.withHand, {
isHandMode: (board, event) => {
const point = toViewBoxPoint(board, toHostPoint(board, event.x, event.y));
const isHitTarget = isHitElement(board, point);
return PlaitBoard.isPointer(board, PlaitPointerType.selection) && !isHitTarget;
}
});
return withNodeIcon(board);
};
class ForceAtlasNodeIconBaseComponent {
initialize() {
if (!this.iconItem.fontSize) {
this.iconItem.fontSize = NODE_ICON_FONT_SIZE;
}
if (!this.iconItem.color) {
this.iconItem.color = DEFAULT_NODE_ICON_COLOR;
}
this.nativeElement().style.fontSize = `${this.iconItem.fontSize}px`;
this.nativeElement().style.color = `${this.iconItem.color}`;
this.nativeElement().classList.add(NODE_ICON_CLASS_NAME);
}
}
/*
* Public API Surface of utils
*/
/**
* Generated bundle index. Do not edit.
*/
export { ACTIVE_BACKGROUND_NODE_ALPHA, ACTIVE_NODE_ICON_CLASS_NAME, ACTIVE_NODE_ICON_FONT_SIZE, DEFAULT_ACTIVE_NODE_SIZE_MULTIPLIER, DEFAULT_ACTIVE_WAVE_NODE_SIZE_MULTIPLIER, DEFAULT_EDGE_PARTICLE_SIZE, DEFAULT_EDGE_STYLES, DEFAULT_LINE_STYLES, DEFAULT_NODE_BACKGROUND_COLOR, DEFAULT_NODE_ICON_COLOR, DEFAULT_NODE_LABEL_FONT_SIZE, DEFAULT_NODE_LABEL_HEIGHT, DEFAULT_NODE_LABEL_MARGIN_TOP, DEFAULT_NODE_LABEL_STYLE, DEFAULT_NODE_LABEL_WIDTH, DEFAULT_NODE_SCALING_RATIO, DEFAULT_NODE_SIZE, DEFAULT_NODE_STYLES, EdgeDirection, ForceAtlasElement, ForceAtlasNodeIconBaseComponent, NODE_ICON_CLASS_NAME, NODE_ICON_FONT_SIZE, NODE_LABEL_CLASS_NAME, SECOND_DEPTH_LINE_ALPHA, SECOND_DEPTH_NODE_ALPHA, withForceAtlas, withNodeIcon };
//# sourceMappingURL=plait-graph-viz.mjs.map