From 766a150c2b61ca97ad7d4fe6d0bf0c17f58e79f8 Mon Sep 17 00:00:00 2001 From: Abhinav Singh Parmar Date: Mon, 31 Jul 2023 19:14:52 +0530 Subject: [PATCH] New Added support for node label alignment --- docs/styles.md | 14 ++++++++ src/models/node.ts | 13 +++++++ src/renderer/canvas/edge/base.ts | 3 +- src/renderer/canvas/label.ts | 12 ++++++- src/renderer/canvas/node.ts | 59 ++++++++++++++++++++++++++++---- 5 files changed, 93 insertions(+), 8 deletions(-) diff --git a/docs/styles.md b/docs/styles.md index 9846ea3..c883400 100644 --- a/docs/styles.md +++ b/docs/styles.md @@ -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 @@ -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 { + TOP = 'top', + BOTTOM = 'bottom', + LEFT = 'left', + RIGHT = 'right', +} +``` + ## Default style values Default node style values are defined as follows: diff --git a/src/models/node.ts b/src/models/node.ts index 320ddb6..b996896 100644 --- a/src/models/node.ts +++ b/src/models/node.ts @@ -32,6 +32,13 @@ export enum NodeShapeType { HEXAGON = 'hexagon', } +export enum NodeLabelAligment { + TOP = 'top', + BOTTOM = 'bottom', + LEFT = 'left', + RIGHT = 'right', +} + /** * Node style properties used to style the node (color, width, label, etc.). */ @@ -59,6 +66,7 @@ export type INodeStyle = Partial<{ size: number; mass: number; zIndex: number; + labelAlignment: NodeLabelAligment; }>; export interface INodeData { @@ -95,6 +103,7 @@ export interface INode { getBorderWidth(): number; getBorderColor(): Color | string | undefined; getBackgroundImage(): HTMLImageElement | undefined; + getLabelAlignment(): NodeLabelAligment; } // TODO: Dirty solution: Find another way to listen for global images, maybe through @@ -363,6 +372,10 @@ export class Node implements INode( const label = new Label(edgeLabel, { position: edge.getCenter(), textBaseline: LabelTextBaseline.MIDDLE, + textAlign: LabelTextAlign.CENTER, properties: { fontBackgroundColor: edge.style.fontBackgroundColor, fontColor: edge.style.fontColor, diff --git a/src/renderer/canvas/label.ts b/src/renderer/canvas/label.ts index 78f0719..2ab1bb6 100644 --- a/src/renderer/canvas/label.ts +++ b/src/renderer/canvas/label.ts @@ -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 { @@ -20,6 +27,7 @@ export interface ILabelProperties { } export interface ILabelData { + textAlign: LabelTextAlign; textBaseline: LabelTextBaseline; position: IPosition; properties: Partial; @@ -33,6 +41,7 @@ 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}`; @@ -40,6 +49,7 @@ export class Label { 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); @@ -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++) { diff --git a/src/renderer/canvas/node.ts b/src/renderer/canvas/node.ts index 814d7a6..61bd102 100644 --- a/src/renderer/canvas/node.ts +++ b/src/renderer/canvas/node.ts @@ -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; @@ -97,9 +110,43 @@ const drawNodeLabel = ( 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; + } + 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,