Creating Interactive Diagrams with Fabric.js: A Step-by-Step Guide
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 = point2[0] - point1[0];
const yDiff = point2[1] - point1[1];
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, points[i]);
if (distance < closestDistance) {
closestPoint = points[i];
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 = currentCornerMetrix[direction];
// Port 2
const port2MiddleCornerName = oppositeCornerMetrix[direction];
line.bounds = line.bounds | | {};
const lineProp = {
bounds: {
[port1MiddleCornerName]: [
(line.bounds[port1MiddleCornerName] | | []).push(clonedShape.id)
],
[port2MiddleCornerName]: [
(line.bounds[port2MiddleCornerName] | | []).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
What is the Difference Between SPAs, SSGs, and SSR?
Confused by SPAs, SSGs, and SSR? Learn the key differences in performance, SEO, and user experience. See why we chose SSG for a 90% performance boost at Shorter Loop.
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 Create Interactive Graphics with 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.
5 Steps to Customize the Rotate Icon in Fabric.js
This guide provides a comprehensive overview of how to customize the rotate icon in Fabric.js, enhancing your canvas projects with unique designs.
Pixel-Perfect 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.
Choosing Between Monoliths, Microservices, and Serverless
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.
7 Tips to Optimize Fabric.js Rendering Performance
Discover key optimization techniques to enhance the performance of Fabric.js. This guide provides practical tips for creating responsive applications.
Creating a Customizable Section Component with Fabric.js 5
Explore how to use Fabric.js 5 to create dynamic sections with draggable textboxes, enhancing your web application's interactivity.
Create Custom Stroked Textboxes in Fabric.js: A Step-by-Step Guide
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 Step-by-Step Guide
In this guide, we explore creating interactive diagrams using Fabric.js by adding ports and connector lines, enhancing user interaction and visual clarity.
Hono.js vs Express.js: 5 Key Differences You Should Know
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.
Mastering Fabric.js 5: 5 Steps to Programmatically Delete Objects
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.
Step-by-Step Guide to Finding Middle Coordinates 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.
Understanding Angular Change Detection: Default vs OnPush
This blog post explores Angular's change detection strategies, comparing OnPush and Default to help developers optimize performance effectively.
Fixing Common Sequelize Association Mistakes: 5 Steps
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.
3 Essential Steps to Create Object Groups in 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.
7 Proven Strategies to Enhance Code Readability and Quality
This article provides effective strategies for simplifying the code review process while explaining how to improve code readability and enhance quality in software development.
5 Steps to Deploy Your Node.js App on AWS EC2
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.
Getting Started with Fabric.js in Angular 13: A Complete Guide
This post is a comprehensive guide to getting started with Fabric.js in your Angular 13 project, covering installation, examples, and editing techniques.
5 Best Practices for Request Cancellation in Web Apps
Discover how to manage and cancel pending API requests effectively using AbortController to enhance performance and user experience.
5 Benefits of Transitioning from AWS Pipeline to GitHub Actions
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.
6 Benefits of Using Feature Flags for Software Development
This guide explores the power of feature flags in software development, highlighting their benefits and practical implementation strategies to enhance user experience.
Mastering Fabric.js: 5 Steps to Add Canvas Event Listeners
This article explores the functionalities of Fabric.js canvas events, showcasing methods to amplify web interactivity and improve user experiences.
Mastering Test Driven Development: 3 Essential Phases
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.
