Controller
The Controller (implemented as the Control class) is the central management system for all annotations in your Ogma graph. It provides methods to add, remove, select, and interact with annotations, as well as an event system to respond to changes.
Overview
Every Ogma Annotations setup starts with creating a Controller instance:
import { Ogma } from "@linkurious/ogma";
import { Control } from "@linkurious/ogma-annotations";
const ogma = new Ogma({ container: "graph-container" });
const controller = new Control(ogma);The controller maintains the state of all annotations and coordinates their rendering with the Ogma instance.
Core Responsibilities
The Controller handles:
- Annotation Management - Adding, removing, and tracking all annotations
- Selection State - Managing which annotation is currently selected
- Event Dispatching - Notifying your code when annotations change
- User Interaction - Handling interactive creation and editing
- Rendering Coordination - Working with Ogma to display annotations
Basic Usage
Adding Annotations
Use the add() method to add annotations to the controller:
import { createArrow, createText } from "@linkurious/ogma-annotations";
const arrow = createArrow(0, 0, 100, 100, { stroke: "#ff6b6b" });
controller.add(arrow);
const text = createText(50, 50, "Label", { fontSize: 16 });
controller.add(text);You can also add multiple annotations at once:
const annotations = [
createArrow(0, 0, 100, 100),
createArrow(0, 0, -100, -100),
createText(0, 0, "Center")
];
annotations.forEach((annotation) => controller.add(annotation));Accessing Annotations
Get all annotations managed by the controller:
const allAnnotations = controller.getAnnotations();
console.log(`Total annotations: ${allAnnotations.length}`);Get a specific annotation by ID:
const annotation = controller.getAnnotation(annotationId);
if (annotation) {
console.log("Found:", annotation);
}Removing Annotations
Remove an annotation by reference:
controller.remove(arrow);Remove by ID:
controller.removeById(annotationId);Remove all annotations:
controller.clear();Selection
The controller maintains a selection state - at most one annotation can be selected at a time.
Getting Selection
const selected = controller.getSelectedAnnotation();
if (selected) {
console.log("Selected annotation:", selected.id);
} else {
console.log("No annotation selected");
}Setting Selection
// Select an annotation
controller.select(arrow);
// Or select by ID
controller.selectById(annotationId);
// Deselect
controller.deselect();Selection Events
Listen for selection changes:
controller.on("select", (annotation) => {
console.log("Selected:", annotation.id);
});
controller.on("deselect", (annotation) => {
console.log("Deselected:", annotation.id);
});Event System
The controller emits events for all significant changes. This allows you to react to user interactions and update your UI accordingly.
Available Events
| Event | Description | Payload |
|---|---|---|
add | An annotation was added | The added annotation |
remove | An annotation was removed | The removed annotation |
update | An annotation was modified | The updated annotation |
select | An annotation was selected | The selected annotation |
deselect | An annotation was deselected | The deselected annotation |
clear | All annotations were removed | undefined |
Listening to Events
// Listen for additions
controller.on("add", (annotation) => {
console.log("New annotation:", annotation);
updateAnnotationList();
});
// Listen for updates
controller.on("update", (annotation) => {
console.log("Modified:", annotation);
refreshUI();
});
// Listen for removals
controller.on("remove", (annotation) => {
console.log("Removed:", annotation.id);
updateCount();
});One-Time Listeners
For events you only need to hear once:
controller.once("add", (annotation) => {
console.log("First annotation added!");
showWelcomeMessage();
});Removing Listeners
const handler = (annotation) => {
console.log("Added:", annotation);
};
// Add listener
controller.on("add", handler);
// Remove listener
controller.off("add", handler);Remove all listeners for an event:
controller.off("add"); // Removes all 'add' listenersInteractive Creation
The controller provides methods to enable user-driven annotation creation:
Enable Arrow Drawing
Use enableArrowDrawing() to let users draw arrows by clicking and dragging:
// Enable arrow drawing mode
controller.enableArrowDrawing({
strokeType: "plain",
strokeColor: "#3498db",
strokeWidth: 2,
head: "arrow"
});
// Listen for completion
controller.once("completeDrawing", (annotation) => {
console.log("Arrow created:", annotation);
});
// Listen for cancellation (e.g., user pressed Escape)
controller.once("cancelDrawing", () => {
console.log("Drawing cancelled");
});When this method is called, the user can:
- Click on the graph to set the arrow's start point
- Drag to set the arrow's end point
- Release to complete the arrow
- Press Escape to cancel
Enable Text Drawing
Use enableTextDrawing() to let users create text annotations:
// Enable text drawing mode
controller.enableTextDrawing({
font: "Arial",
fontSize: 16,
color: "#2c3e50",
background: "#ffffff",
borderRadius: 4,
padding: 8
});
// Listen for completion
controller.once("completeDrawing", (annotation) => {
console.log("Text created:", annotation);
});
// Listen for cancellation
controller.once("cancelDrawing", () => {
console.log("Drawing cancelled");
});When this method is called, the user can:
- Click on the graph to place the text
- Drag to size the text box
- Type to edit the content
- Click outside or press Enter to complete
- Press Escape to cancel
Cancel Drawing
To programmatically cancel an ongoing drawing operation:
controller.cancelDrawing();This will trigger the cancelDrawing event.
Controller Options
When creating a controller, you can pass options:
const controller = new Control(ogma, {
// Options will be added here in future versions
});Currently, the controller uses sensible defaults and doesn't require configuration.
Advanced: Controller State
The controller maintains internal state about:
- All annotations - The complete list of managed annotations
- Selection - The currently selected annotation (if any)
- Interaction mode - Whether the user is currently creating/editing an annotation
You generally don't need to access this state directly, but it's useful to understand:
// Check if something is selected
if (controller.getSelectedAnnotation()) {
console.log("User has selected an annotation");
}
// Check annotation count
const count = controller.getAnnotations().length;
console.log(`Managing ${count} annotations`);Lifecycle and Cleanup
Initialization
The controller is ready to use immediately after creation:
const controller = new Control(ogma);
// Ready to use!Cleanup
When you're done with the controller (e.g., unmounting a component or destroying the graph):
// Remove all annotations and clean up
controller.destroy();This will:
- Remove all annotations from the display
- Remove all event listeners
- Clean up internal state
Destroy Before Ogma
If you're also destroying the Ogma instance, destroy the controller first:
controller.destroy();
ogma.destroy();Best Practices
1. One Controller Per Ogma Instance
// ✅ Good: One controller per Ogma instance
const ogma = new Ogma({ container: "graph" });
const controller = new Control(ogma);
// ❌ Avoid: Multiple controllers for the same Ogma instance
const controller1 = new Control(ogma);
const controller2 = new Control(ogma); // Don't do this!2. Use Events for UI Updates
// ✅ Good: React to events
controller.on("add", () => {
updateAnnotationCount();
refreshUI();
});
// ❌ Avoid: Polling
setInterval(() => {
const count = controller.getAnnotations().length;
// This is inefficient
}, 1000);3. Clean Up When Done
// ✅ Good: Clean up properly
function cleanup() {
controller.destroy();
ogma.destroy();
}
// In a React component
useEffect(() => {
const controller = new Control(ogma);
return () => controller.destroy(); // Cleanup on unmount
}, [ogma]);4. Separate Concerns
// ✅ Good: Keep UI logic separate
controller.on("select", (annotation) => {
updateStylePanel(annotation);
highlightInList(annotation);
});
// Business logic
controller.on("add", (annotation) => {
logAnalytics("annotation_created", annotation.type);
saveToBackend(annotation);
});Examples
Complete Setup with Events
import { Ogma } from "@linkurious/ogma";
import { Control, createArrow, createText } from "@linkurious/ogma-annotations";
// Initialize
const ogma = new Ogma({ container: "graph-container" });
const controller = new Control(ogma);
// Set up event handlers
controller.on("add", (annotation) => {
console.log("Added:", annotation.type, annotation.id);
document.getElementById("count").textContent = String(
controller.getAnnotations().length
);
});
controller.on("select", (annotation) => {
console.log("Selected:", annotation.id);
showStylePanel(annotation);
});
controller.on("remove", (annotation) => {
console.log("Removed:", annotation.id);
hideStylePanelIfEmpty();
});
// Add some annotations
controller.add(createArrow(0, 0, 100, 100, { stroke: "#e74c3c" }));
controller.add(createText(50, 50, "Example", { fontSize: 16 }));
// Cleanup function
function cleanup() {
controller.destroy();
ogma.destroy();
}Interactive Annotation Creation
function enableDrawingMode(type: "arrow" | "text") {
if (type === "arrow") {
controller.enableArrowDrawing({
strokeType: "plain",
strokeColor: "#3498db",
strokeWidth: 2,
head: "arrow"
});
} else {
controller.enableTextDrawing({
font: "Arial",
fontSize: 14,
color: "#2c3e50",
background: "#ffffff",
padding: 8
});
}
// Handle completion or cancellation
const cleanup = () => {
console.log("Drawing finished");
};
controller.once("completeDrawing", cleanup);
controller.once("cancelDrawing", cleanup);
}
// Usage with buttons
document.getElementById("add-arrow-btn").addEventListener("click", () => {
enableDrawingMode("arrow");
});
document.getElementById("add-text-btn").addEventListener("click", () => {
enableDrawingMode("text");
});
// Cancel with Escape key
document.addEventListener("keydown", (evt) => {
if (evt.key === "Escape") {
controller.cancelDrawing();
}
});API Reference
For complete API documentation including all methods, parameters, and return types, see:
Next Steps
- Learn about Annotations - Understand Arrow and Text types
- Events Deep Dive - Master the event system
- Creating Annotations - Add annotations programmatically and interactively