Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Added support for node label alignment #78

Open
wants to merge 1 commit into
base: release/1.0.0
Choose a base branch
from
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
14 changes: 14 additions & 0 deletions docs/styles.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ the following properties:
| `size` | number | Node size (usually the radius). The default is `5`. |
| `mass` | number | Node mass. _(Currently not used)_ |
| `zIndex` | number | Specifies the stack order of an element during rendering. The default is `0`. |
| `labelAlignment` | NodeLabelAligment | Node label alignment enum. Possible values are: `top`, `bottom`, `left`, `right`. Default is `NodeLabelAligment.BOTTOM` |

## Shape enumeration

Expand All @@ -65,6 +66,19 @@ export enum NodeShapeType {
}
```

## Label alignment enumeration

The enum `NodeLabelAligment` which is used for the node `label alignment` property is defined as:

```typescript
export enum NodeLabelAligment {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A typo, missing n -> Alig + n + ment

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check other usage of NodeLabelAlignment because the typo is there too.

TOP = 'top',
BOTTOM = 'bottom',
LEFT = 'left',
RIGHT = 'right',
}
```

## Default style values

Default node style values are defined as follows:
Expand Down
13 changes: 13 additions & 0 deletions src/models/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ export enum NodeShapeType {
HEXAGON = 'hexagon',
}

export enum NodeLabelAligment {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When thinking about this, I feel like this should be called NodeLabelPosition. At first (before running it), I thought this aligns the label, and the text within the label, e.g.

LEFT ALIGNMENT 
This is
multiline
text
RIGHT ALIGNMENT 
  This is
multiline
     text

So I would propose renaming this to NodeLabelPosition because it is actually a label position, rather than alignment.

TOP = 'top',
BOTTOM = 'bottom',
LEFT = 'left',
RIGHT = 'right',
}

/**
* Node style properties used to style the node (color, width, label, etc.).
*/
Expand Down Expand Up @@ -59,6 +66,7 @@ export type INodeStyle = Partial<{
size: number;
mass: number;
zIndex: number;
labelAlignment: NodeLabelAligment;
}>;

export interface INodeData<N extends INodeBase> {
Expand Down Expand Up @@ -95,6 +103,7 @@ export interface INode<N extends INodeBase, E extends IEdgeBase> {
getBorderWidth(): number;
getBorderColor(): Color | string | undefined;
getBackgroundImage(): HTMLImageElement | undefined;
getLabelAlignment(): NodeLabelAligment;
}

// TODO: Dirty solution: Find another way to listen for global images, maybe through
Expand Down Expand Up @@ -363,6 +372,10 @@ export class Node<N extends INodeBase, E extends IEdgeBase> implements INode<N,
});
}

getLabelAlignment(): NodeLabelAligment {
return this.style.labelAlignment ?? NodeLabelAligment.BOTTOM;
}

protected _isPointInBoundingBox(point: IPosition): boolean {
return isPointInRectangle(this.getBoundingBox(), point);
}
Expand Down
3 changes: 2 additions & 1 deletion src/renderer/canvas/edge/base.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { INodeBase } from '../../../models/node';
import { IEdge, EdgeCurved, EdgeLoopback, EdgeStraight, IEdgeBase } from '../../../models/edge';
import { IPosition } from '../../../common';
import { drawLabel, Label, LabelTextBaseline } from '../label';
import { drawLabel, Label, LabelTextAlign, LabelTextBaseline } from '../label';
import { drawCurvedLine, getCurvedArrowShape } from './types/edge-curved';
import { drawLoopbackLine, getLoopbackArrowShape } from './types/edge-loopback';
import { drawStraightLine, getStraightArrowShape } from './types/edge-straight';
Expand Down Expand Up @@ -55,6 +55,7 @@ const drawEdgeLabel = <N extends INodeBase, E extends IEdgeBase>(
const label = new Label(edgeLabel, {
position: edge.getCenter(),
textBaseline: LabelTextBaseline.MIDDLE,
textAlign: LabelTextAlign.CENTER,
properties: {
fontBackgroundColor: edge.style.fontBackgroundColor,
fontColor: edge.style.fontColor,
Expand Down
12 changes: 11 additions & 1 deletion src/renderer/canvas/label.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ const FONT_LINE_SPACING = 1.2;
export enum LabelTextBaseline {
TOP = 'top',
MIDDLE = 'middle',
BOTTOM = 'bottom',
}

export enum LabelTextAlign {
LEFT = 'left',
RIGHT = 'right',
CENTER = 'center',
}

export interface ILabelProperties {
Expand All @@ -20,6 +27,7 @@ export interface ILabelProperties {
}

export interface ILabelData {
textAlign: LabelTextAlign;
textBaseline: LabelTextBaseline;
position: IPosition;
properties: Partial<ILabelProperties>;
Expand All @@ -33,13 +41,15 @@ export class Label {
public readonly fontSize: number = DEFAULT_FONT_SIZE;
public readonly fontFamily: string = getFontFamily(DEFAULT_FONT_SIZE, DEFAULT_FONT_FAMILY);
public readonly textBaseline: LabelTextBaseline;
public readonly textAlign: LabelTextAlign;

constructor(text: string, data: ILabelData) {
this.text = `${text === undefined ? '' : text}`;
this.textLines = splitTextLines(this.text);
this.position = data.position;
this.properties = data.properties;
this.textBaseline = data.textBaseline;
this.textAlign = data.textAlign;

if (this.properties.fontSize !== undefined || this.properties.fontFamily) {
this.fontSize = Math.max(this.properties.fontSize ?? 0, 0);
Expand Down Expand Up @@ -99,7 +109,7 @@ const drawText = (context: CanvasRenderingContext2D, label: Label) => {
context.fillStyle = (label.properties.fontColor ?? DEFAULT_FONT_COLOR).toString();
context.font = label.fontFamily;
context.textBaseline = label.textBaseline;
context.textAlign = 'center';
context.textAlign = label.textAlign;
const lineHeight = label.fontSize * FONT_LINE_SPACING;

for (let i = 0; i < label.textLines.length; i++) {
Expand Down
59 changes: 53 additions & 6 deletions src/renderer/canvas/node.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
import { INodeBase, INode, NodeShapeType } from '../../models/node';
import { IEdgeBase } from '../../models/edge';
import { drawDiamond, drawHexagon, drawSquare, drawStar, drawTriangleDown, drawTriangleUp, drawCircle } from './shapes';
import { drawLabel, Label, LabelTextBaseline } from './label';
import {
INodeBase,
INode,
NodeShapeType,
NodeLabelAligment,
} from "../../models/node";
import { IEdgeBase } from "../../models/edge";
import {
drawDiamond,
drawHexagon,
drawSquare,
drawStar,
drawTriangleDown,
drawTriangleUp,
drawCircle,
} from "./shapes";
import { drawLabel, Label, LabelTextAlign, LabelTextBaseline } from "./label";

// The label will be `X` of the size below the Node
const DEFAULT_LABEL_DISTANCE_SIZE_FROM_NODE = 0.2;
Expand Down Expand Up @@ -97,9 +110,43 @@ const drawNodeLabel = <N extends INodeBase, E extends IEdgeBase>(
const center = node.getCenter();
const distance = node.getBorderedRadius() * (1 + DEFAULT_LABEL_DISTANCE_SIZE_FROM_NODE);

let labelX = center.x;
let labelY = center.y;
let labelTextAlign: LabelTextAlign;
let labelTextBaseline: LabelTextBaseline;

switch (node.getLabelAlignment()) {
case NodeLabelAligment.BOTTOM:
labelY += distance;
labelTextAlign = LabelTextAlign.CENTER;
labelTextBaseline = LabelTextBaseline.TOP;
break;
case NodeLabelAligment.TOP:
labelY -= distance;
labelTextAlign = LabelTextAlign.CENTER;
labelTextBaseline = LabelTextBaseline.BOTTOM;
break;
case NodeLabelAligment.LEFT:
labelX -= distance;
labelTextAlign = LabelTextAlign.RIGHT;
labelTextBaseline = LabelTextBaseline.MIDDLE;
break;
case NodeLabelAligment.RIGHT:
labelX += distance;
labelTextAlign = LabelTextAlign.LEFT;
labelTextBaseline = LabelTextBaseline.MIDDLE;
break;
default:
labelY += distance;
labelTextAlign = LabelTextAlign.CENTER;
labelTextBaseline = LabelTextBaseline.TOP;
break;
}
Comment on lines +118 to +144
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this should be part of Label logic. I would propose the following:

NodeLabelPosition.{TOP, BOTTOM, LEFT, RIGHT} // default = BOTTOM
NodeLabelAlignment.{LEFT, RIGHT, CENTER} // default = CENTER

EdgeLabelPosition.{CENTER} // default = CENTER
EdgeLabelAlignment.{LEFT, RIGHT, CENTER} // default = CENTER

And then Label logic receives position + alignment and handles the rest. Please take care of multiple lines because it is not working well. Check the image below for the "top".

Screenshot 2023-08-16 at 22 22 31

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Node logic should just handle the start position: x, y and give that to the Label logic. Label should take the start position + label position + label alignment + text lines to figure out where each line should be.


const label = new Label(nodeLabel, {
position: { x: center.x, y: center.y + distance },
textBaseline: LabelTextBaseline.TOP,
position: { x: labelX, y: labelY },
textBaseline: labelTextBaseline,
textAlign: labelTextAlign,
properties: {
fontBackgroundColor: node.style.fontBackgroundColor,
fontColor: node.style.fontColor,
Expand Down