Creating Interactive Diagrams with Fabric.js: A Guide to Ports and Connector Lines
In this guide, we explore creating interactive diagrams using Fabric.js by adding ports and connector lines, enhancing user interaction and visual clarity.
Welcome to the second part of our Fabric.js “Mastering Fabric.js: Tips and Tricks for Object Coordinates and Connecting Lines” series where we dive deeper into the world of interactive diagrams.
In our previous blog post, "Finding the Middle Coordinates of the Vertices of a Polygon in Fabric.js", we explored how to add middle points to a polygon and create a parallelogram with circles located at its vertices. Now, we will take our interactive diagrams to the next level by adding ports and connector lines. In this blog post, "Creating Interactive Diagrams with Fabric.js: A Guide to Ports and Connector Lines", we will guide you through the steps to create a function that modifies the placement of the circles in real-time as the parallelogram undergoes movement, resizing, skewing, or rotation.
Finding the Middle Coordinates of the Vertices of a Polygon in Fabric.js
With this new knowledge, you can create interactive diagrams that are not only informative but also engaging for your audience. So let's get started!
Ports and connectors are essential elements in interactive diagrams created with Fabric.js because they enable the creation of a dynamic and interactive user interface. Ports represent the points of connection on an object or diagram, while connectors are the lines or paths that connect these points. By creating ports and connectors, you can allow users to interact with your diagrams by dragging and dropping objects, changing their size or position, and visually connecting them through the use of connectors.
Ports and connectors are commonly used in a variety of applications, including flowcharts, diagrams, and network topologies. They allow users to create visual representations of complex systems, making it easier to understand and visualize information. Furthermore, they enable users to simulate different scenarios, test hypotheses, and explore different solutions to problems.
Overall, ports and connectors are powerful tools that can enhance the usability and interactivity of your Fabric.js diagrams, making them more engaging, informative, and useful for your users.
As you go further, you'll find a series of steps to create interactive diagrams using Fabric.js:
- Firstly, we will create a main polygon, which you can refer to in our previous blog.
- Next, we will add target points, also known as ports, to the main shape. You can refer to our previous blog for more information on this step.
- We will update the polygon and its ports when the object is scaled, skewed, rotated, or moved. These icons are placed on every middle edge point and allow you to clone and add the current group/shape to the canvas.
const CONTROL_FLOW_ICON =
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='8px' viewBox='0 0 24 24' width='8px' fill='%23573DF4'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2z'/%3E%3C/svg%3E";
const TOLERANCE_VALUE = 20;
function renderIcon(icon) {
return function renderIcon(ctx, left, top, styleOverride, fabricObject) {
const size = this.cornerSize;
ctx.save();
ctx.translate(left, top);
ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
ctx.drawImage(icon, -size / 2, -size / 2, size, size);
ctx.restore();
};
}
function createTopControlFlowObject(e) {
const object = canvas.getActiveObject();
const pointer: any = canvas.getPointer(e);
const origY = pointer.y;
const prop = {
top: origY - object.height - 2 * TOLERANCE_VALUE
};
setClonedObjectPosition(object, prop, pointer);
}
function createRightControlFlowObject(e) {
const object = canvas.getActiveObject();
const pointer: any = canvas.getPointer(e);
const origX = pointer.x;
const prop = {
left: origX + 2 * TOLERANCE_VALUE
};
setClonedObjectPosition(object, prop, pointer);
}
function createLeftControlFlowObject(e, target) {
const object = canvas.getActiveObject();
const pointer: any = canvas.getPointer(e);
const origX = pointer.x;
const prop = {
left: origX - object.width - 2 * TOLERANCE_VALUE
};
setClonedObjectPosition(object, prop, pointer);
}
function createBottomControlFlowObject(e) {
const object = canvas.getActiveObject();
const pointer: any = canvas.getPointer(e);
const origY = pointer.y;
const prop = {
top: origY + 2 * TOLERANCE_VALUE
};
setClonedObjectPosition(object, prop, pointer);
}
function addFlowPoints(object: any): any {
const controlFlowIcon = CONTROL_FLOW_ICON;
const controlFlowImg = document.createElement("img");
controlFlowImg.src = controlFlowIcon;
const bottomFlowControl = {
key: "bottom",
x: object.controls.mb.x,
y: object.controls.mb.y,
offsetY: 1.5 * TOLERANCE_VALUE,
mouseUpHandler: createBottomControlFlowObject
};
const topFlowControl = {
key: "top",
x: object.controls.mt.x,
y: object.controls.mt.y,
offsetY: -1.5 * TOLERANCE_VALUE,
mouseUpHandler: createTopControlFlowObject
};
const leftFlowControl = {
key: "left",
x: object.controls.ml.x,
y: object.controls.ml.y,
offsetX: -1.5 * TOLERANCE_VALUE,
mouseUpHandler: createLeftControlFlowObject
};
const rightFlowControl = {
key: "right",
x: object.controls.mr.x,
y: object.controls.mr.y,
offsetX: 1.5 * TOLERANCE_VALUE,
mouseUpHandler: createRightControlFlowObject
};
[
bottomFlowControl,
topFlowControl,
leftFlowControl,
rightFlowControl
].forEach(
(contol) =>
(fabric.Object.prototype.controls[
`${contol.key}ControlFlowControl`
] = new fabric.Control({
cursorStyle: "crosshair",
//@ts-ignore
render: renderIcon(controlFlowImg),
//@ts-ignore
cornerSize: 8,
...contol
}))
);
if (object && object.item && object.item(0)) {
addPortsToPolygon(object.item(0));
}
canvas.requestRenderAll();
}
Ref.: https://github.com/dinesh-rawat-dev/fabricjs-port-connector-library/blob/main/src/index.ts#L332
4. In this step, we will clone the current shape, along with its control points and ports, and then point the clone shape to the ports of the original shape.
To accomplish this, we will first create a function that clones the original shape and its control points using the clone() method in Fabric.js. Next, we will loop through each port of the original shape and create a corresponding port on the clone shape using the addPort() method.
function createClonedObjectAndAddConnector(object: any, prop, controlClicked) {
const direction = object.__corner;
const allHintCirclesForPort1 = canvas
.getObjects()
.filter(
(object) =>
object &&
object.isType("circle") &&
//@ts-ignore
object.className === "control_flow_points"
)
.map((object) => object.left, object.top);
//@ts-ignore
let currentShapeCoords = allHintCirclesForPort1.slice();
const sourcePoint: any = getClosestPoint(
controlClicked.x, controlClicked.y,
currentShapeCoords
);
object.clone((cloned: any) => {
cloned.set(prop);
cloned.id = Date.now();
cloned.parent = [];
cloned.children = [];
const textbox = cloned.item(1);
textbox.text = `${cloned.id}`;
canvas.add(cloned);
canvas.discardActiveObject();
canvas.setActiveObject(cloned);
canvas.requestRenderAll();
addFlowPoints(cloned);
addPortsToPolygon(cloned.item(0));
setTimeout((_) => {
addConnector(canvas, object, direction, cloned, sourcePoint);
});
});
}
5. Create a connector line between the main shape and its cloned shape by locating the ports of each shape and adding a line between them. To accomplish this, we will first use the addConnector() method to find the coordinates of the ports on each shape. Next, we will use the drawLine() method to draw a line between the two port coordinates.
Note: Determine which port the connector line will be connected to using the Euclidean formula. This formula calculates the distance between two points in a two-dimensional plane. I will provide an example array of coordinates to demonstrate how this formula works. By using this formula, we can accurately place the connector line between the two shapes based on the user's selection.
function getDistance(point1, point2) {
const xDiff = point20 - point10;
const yDiff = point21 - point11;
return Math.sqrt(xDiff * xDiff + yDiff * yDiff);
}
function getClosestPoint(point, points) {
let closestPoint = null;
let closestDistance = Infinity;
for (let i = 0; i < points.length; i++) {
const distance = getDistance(point, pointsi);
if (distance < closestDistance) {
closestPoint = pointsi;
closestDistance = distance;
}
}
return closestPoint;
}
function drawLine(
canvas,
fromPoint,
toPoint,
mainShape,
clonedShape,
direction
) {
const line: any = new fabric.Line(...fromPoint, ...toPoint, {
stroke: "black",
strokeWidth: 2,
centeredRotation: true,
centeredScaling: true
});
line.id = Date.now();
canvas.add(line);
line.sendToBack();
mainShape.children = mainShape.children | | [];
mainShape.children.push({
connector: line.id,
id: clonedShape.id
});
const mainShapeProp = {
children: mainShape.children
};
addCustomPropertyToFabric(mainShape, mainShapeProp);
clonedShape.parent = clonedShape.parent | | [];
clonedShape.parent.push({
connector: line.id,
id: mainShape.id
});
const cloneShapeProp = {
parent: clonedShape.parent
};
addCustomPropertyToFabric(clonedShape, cloneShapeProp);
// Port 1
const port1MiddleCornerName = currentCornerMetrixdirection;
// Port 2
const port2MiddleCornerName = oppositeCornerMetrixdirection;
line.bounds = line.bounds | | {};
const lineProp = {
bounds: {
port1MiddleCornerName: [
(line.boundsport1MiddleCornerName | | []).push(clonedShape.id)
],
port2MiddleCornerName: [
(line.boundsport2MiddleCornerName | | []).push(mainShape.id)
]
}
};
line.set(lineProp);
addCustomPropertyToFabric(line, {
lineProp
});
canvas.requestRenderAll();
}
6. Lastly, we need to adjust the lines when the shapes/group is moving:
canvas.on("object:moving", (e: any) => {
const object = e.target;
if (object.children && object.children.length) {
adjustAssociatedNodes(object.children, "x1", "y1");
}
if (object.parent && object.parent.length) {
adjustAssociatedNodes(object.parent, "x2", "y2");
console.log(object.parent.length);
}
});
In this blog, we have explored
- How to create interactive diagrams with Fabric.js, specifically focusing on ports and connector lines.
- Building upon the previous blog "Finding the Middle Coordinates of the Vertices of a Polygon in Fabric.js", we have learned how to add ports to a polygon and update their position in real time as the shape is manipulated.
- Additionally, we have explored how to clone a shape and add a connector line between the original shape and its clone by using the Euclidean formula to determine the placement of the connector line.
Overall, Fabric.js provides a powerful toolset for creating interactive diagrams, and the techniques explored in this blog can be used to create a wide range of diagrams and visualizations. By following the steps outlined in this blog, readers can create their own interactive diagrams and explore the full capabilities of Fabric.js.
Codebase:
https://github.com/dinesh-rawat-dev/fabricjs-port-connector-library
Demo: https://codesandbox.io/s/github/dinesh-rawat-dev/fabricjs-port-connector-library
This article answers:
https://github.com/fabricjs/fabric.js/discussions/8732
https://github.com/fabricjs/fabric.js/discussions/8718
https://github.com/fabricjs/fabric.js/issues/2779
https://stackoverflow.com/questions/58063289/fabricjs-function-to-join-2-objects-with-line
https://stackoverflow.com/questions/56800134/unable-to-connect-endpoints-by-line-in-fabric-js
https://stackoverflow.com/questions/20418153/connecting-two-canvas-objects-in-fabric-js
Why Static Site Generation Beats SPAs for SEO | What is a static site generator?
A static website generator builds fast, SEO-friendly HTML pages automatically. Discover the pros and cons of using one for modern sites and apps.
HOW TO Effortlessly Publish Your JavaScript Project as an NPM Module
Effortlessly Publish Your JavaScript Project as an NPM Module with our step-by-step guide. Discover how to share your work and boost visibility today!
A Comprehensive Guide to Working with Objects and Shapes in Fabric.js
This guide explores the extensive capabilities of Fabric.js in creating and manipulating objects and shapes on the web. Learn how to bring your web graphics to life.
A Comprehensive Step-by-Step Guide: Customizing Rotate Icons in Fabric.js for Programmers
This guide provides a comprehensive overview of how to customize the rotate icon in Fabric.js, enhancing your canvas projects with unique designs.
Achieving Pixel-Perfect Alignment: Exploring Object Snapping with SnappyRect in Fabric.js
This article explores the SnappyRect class in Fabric.js, a custom solution for achieving pixel-perfect object alignment through object snapping.
Angular vs React: Which Framework Wins for Scalable Enterprise Apps?
This blog post compares Angular and React, analyzing their strengths and weaknesses for building scalable enterprise applications, helping you choose the right framework for your project.
Best NodeJS Architecture for Scalable Cloud Apps: Monolith vs Microservices vs Serverless (Shorterloop’s Guide)
Discover Shorterloop's insights on the best NodeJS architectures for building scalable cloud applications. We compare monolithic, microservices, and serverless approaches, providing real examples to help you decide.
Boosting Performance in Fabric.js 5: Essential Optimization Techniques and Tips
Discover key optimization techniques to enhance the performance of Fabric.js. This guide provides practical tips for creating responsive applications.
Create Dynamic Sections with Drag-and-Drop Functionality and Customizable Textboxes
Explore how to use Fabric.js 5 to create dynamic sections with draggable textboxes, enhancing your web application's interactivity.
Create Professional-Looking Textboxes in Fabric.js with Strokes and Padding
This blog post covers how to create professional-looking stroked textboxes in Fabric.js, including customization options for padding and rounded corners.
Creating Interactive Diagrams with Fabric.js: A Guide to Ports and Connector Lines
In this guide, we explore creating interactive diagrams using Fabric.js by adding ports and connector lines, enhancing user interaction and visual clarity.
Express.js vs Hono.js: The Ultimate Framework Showdown for Node.js Developers
This blog post compares Express.js and Hono.js, examining their features, performance, and suitability for modern web development. Make an informed choice for your next project.
Fabric.js 5 Mastery: Effortlessly Deleting Selected Objects Programmatically
This blog post explores how to programmatically delete selected objects in Fabric.js 5, focusing on removing polygons and circles. Discover the remove method and practical implementations.
Finding the Middle Coordinates of the Vertices of a Polygon in Fabric.js
This blog post explains how to find the middle coordinates of a polygon's vertices in Fabric.js, simplifying object manipulation for developers. It offers a step-by-step guide and code examples for better understanding.
Optimizing Angular Performance: Change Detection Strategy OnPush vs Default
This blog post explores Angular's change detection strategies, comparing OnPush and Default to help developers optimize performance effectively.
Sequelize Associations: How a Misplaced belongsTo Broke Our Query (And How to Fix It)
This blog post explores a common mistake in Sequelize associations involving belongsTo and belongsToMany, detailing how this led to unexpected SQL query errors and their solutions.
Simplify Object Manipulation: Grouping and Ungrouping with Fabric.js
Learn how to simplify object manipulation in Fabric.js by grouping and ungrouping objects. This guide provides syntax and examples for effective canvas management.
Simplifying Code Review: Tips for Improving Readability
This article provides effective strategies for simplifying the code review process while improving code readability and quality in software development.
Step-by-Step Guide to Deploy NodeJS app with PM2 on AWS EC2 instance
This comprehensive guide walks you through the process of deploying a Node.js application on an AWS EC2 instance using PM2. Explore the crucial steps and benefits.
Step-by-Step Guide to Getting Started with Fabric.js in Angular 13
This post is a comprehensive guide to getting started with Fabric.js in your Angular 13 project, covering installation, examples, and editing techniques.
Stop Wasting API Calls: How to Cancel Pending Requests Like a Pro
Discover how to manage and cancel pending API requests effectively using AbortController to enhance performance and user experience.
Streamlining Your Node.js Projects: How GitHub Actions Can Revolutionize Your Workflow
This blog post explores the benefits of migrating from AWS Pipeline to GitHub Actions for Node.js projects, highlighting improved workflow management and automation capabilities.
The Power of Feature Flags: A Guide to Unlocking the Potential of Your Software
This guide explores the power of feature flags in software development, highlighting their benefits and practical implementation strategies to enhance user experience.
Unleashing the Power of Fabric.js Canvas Events: Elevating Interactivity and User Experience
This article explores the functionalities of Fabric.js canvas events, showcasing methods to amplify web interactivity and improve user experiences.
What is Test Driven Development (TDD)? Definition, Benefits, Example, and more!
Learn about Test Driven Development (TDD), a pivotal software development method that enhances code quality through the Red/Green/Refactor cycle, alongside its benefits and limitations.