Interactive Annotation Creation
Learn how to enable users to create annotations by clicking and dragging on the graph.
Overview
The React wrapper provides the same drawing methods as the core package, accessible through the editor from the context. Users can create annotations interactively using button clicks.
Enable Arrow Drawing
Use editor.enableArrowDrawing() to let users draw arrows:
import { useAnnotationsContext } from "@linkurious/ogma-annotations-react";
function AddArrowButton() {
const { editor } = useAnnotationsContext();
const handleClick = React.useCallback(() => {
editor.enableArrowDrawing({
strokeType: "plain",
strokeColor: "#3498db",
strokeWidth: 2,
head: "arrow"
});
}, [editor]);
return <button onClick={handleClick}>Add Arrow</button>;
}When clicked, the user can:
- Click on the graph to set the arrow start point
- Drag to position the end point
- Release to complete the arrow
- Press Escape to cancel
Enable Text Drawing
Use editor.enableTextDrawing() to let users create text annotations:
function AddTextButton() {
const { editor } = useAnnotationsContext();
const handleClick = React.useCallback(() => {
editor.enableTextDrawing({
font: "Arial",
fontSize: 16,
color: "#2c3e50",
background: "#ffffff",
borderRadius: 4,
padding: 8
});
}, [editor]);
return <button onClick={handleClick}>Add Text</button>;
}When clicked, 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 finish
- Press Escape to cancel
Other Drawing Modes
Box Drawing
function AddBoxButton() {
const { editor } = useAnnotationsContext();
const handleClick = React.useCallback(() => {
editor.enableBoxDrawing({
background: "#f0f0f0",
borderRadius: 8,
padding: 12
});
}, [editor]);
return <button onClick={handleClick}>Add Box</button>;
}Polygon Drawing
function AddPolygonButton() {
const { editor } = useAnnotationsContext();
const handleClick = React.useCallback(() => {
editor.enablePolygonDrawing({
strokeColor: "#3498db",
strokeWidth: 2,
background: "rgba(52, 152, 219, 0.2)"
});
}, [editor]);
return <button onClick={handleClick}>Add Polygon</button>;
}With polygons, users:
- Click points to create vertices
- Press Escape to finish the polygon
Comment Drawing
Comments are text annotations with arrows:
function AddCommentButton() {
const { editor } = useAnnotationsContext();
const handleClick = React.useCallback(() => {
editor.enableCommentDrawing({
offsetX: 200,
offsetY: -150,
commentStyle: {
content: "",
style: {
color: "#2c3e50",
background: "#ffffff",
fontSize: 16,
font: "Arial"
}
},
arrowStyle: {
style: {
strokeColor: "#3498db",
strokeWidth: 2,
head: "arrow"
}
}
});
}, [editor]);
return <button onClick={handleClick}>Add Comment</button>;
}Listening to Drawing Events
The editor emits events during the drawing process:
function DrawingControls() {
const { editor } = useAnnotationsContext();
const handleAddArrow = React.useCallback(() => {
editor.enableArrowDrawing({
strokeColor: "#3498db",
strokeWidth: 2,
head: "arrow"
});
// Listen for completion
editor.once("completeDrawing", (annotation) => {
console.log("Arrow created:", annotation);
});
// Listen for cancellation
editor.once("cancelDrawing", () => {
console.log("Drawing cancelled");
});
}, [editor]);
return <button onClick={handleAddArrow}>Add Arrow</button>;
}Cancel Drawing
Allow users to cancel drawing with a button or keyboard shortcut:
function DrawingControls() {
const { cancelDrawing } = useAnnotationsContext();
// Cancel with Escape key
React.useEffect(() => {
const handleKeyDown = (evt: KeyboardEvent) => {
if (evt.key === "Escape") {
cancelDrawing();
}
};
document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, [cancelDrawing]);
return <button onClick={cancelDrawing}>Cancel Drawing</button>;
}Complete Toolbar Example
Here's a complete toolbar with all drawing modes:
import React from "react";
import { useAnnotationsContext } from "@linkurious/ogma-annotations-react";
function AnnotationToolbar() {
const { editor, cancelDrawing } = useAnnotationsContext();
const handleArrow = React.useCallback(() => {
editor.enableArrowDrawing({
strokeType: "plain",
strokeColor: "#3498db",
strokeWidth: 2,
head: "arrow"
});
}, [editor]);
const handleText = React.useCallback(() => {
editor.enableTextDrawing({
font: "Arial",
fontSize: 16,
color: "#2c3e50",
background: "#ffffff",
padding: 8
});
}, [editor]);
const handleBox = React.useCallback(() => {
editor.enableBoxDrawing({
background: "#f0f0f0",
borderRadius: 8
});
}, [editor]);
const handlePolygon = React.useCallback(() => {
editor.enablePolygonDrawing({
strokeColor: "#3498db",
strokeWidth: 2,
background: "rgba(52, 152, 219, 0.2)"
});
}, [editor]);
// Cancel drawing on Escape
React.useEffect(() => {
const handleKeyDown = (evt: KeyboardEvent) => {
if (evt.key === "Escape") {
cancelDrawing();
}
};
document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, [cancelDrawing]);
return (
<div className="toolbar">
<button onClick={handleArrow}>Add Arrow</button>
<button onClick={handleText}>Add Text</button>
<button onClick={handleBox}>Add Box</button>
<button onClick={handlePolygon}>Add Polygon</button>
<button onClick={cancelDrawing}>Cancel</button>
</div>
);
}
export default AnnotationToolbar;Button States
Track drawing state to provide visual feedback:
function SmartToolbar() {
const { editor } = useAnnotationsContext();
const [activeMode, setActiveMode] = React.useState<string | null>(null);
const enableMode = React.useCallback(
(mode: string, enableFn: () => void) => {
setActiveMode(mode);
enableFn();
const cleanup = () => setActiveMode(null);
editor.once("completeDrawing", cleanup);
editor.once("cancelDrawing", cleanup);
},
[editor]
);
const handleArrow = React.useCallback(() => {
enableMode("arrow", () =>
editor.enableArrowDrawing({ strokeColor: "#3498db" })
);
}, [editor, enableMode]);
const handleText = React.useCallback(() => {
enableMode("text", () => editor.enableTextDrawing({ fontSize: 16 }));
}, [editor, enableMode]);
return (
<div className="toolbar">
<button
className={activeMode === "arrow" ? "active" : ""}
onClick={handleArrow}
>
Add Arrow
</button>
<button
className={activeMode === "text" ? "active" : ""}
onClick={handleText}
>
Add Text
</button>
</div>
);
}.toolbar button.active {
background-color: #3498db;
color: white;
}Using Current Styles
Use the current style from context for new annotations:
function StyledToolbar() {
const { editor, arrowStyle, textStyle } = useAnnotationsContext();
const handleArrow = React.useCallback(() => {
// Use current arrow style
editor.enableArrowDrawing(arrowStyle);
}, [editor, arrowStyle]);
const handleText = React.useCallback(() => {
// Use current text style
editor.enableTextDrawing(textStyle);
}, [editor, textStyle]);
return (
<>
<button onClick={handleArrow}>Add Arrow (Current Style)</button>
<button onClick={handleText}>Add Text (Current Style)</button>
</>
);
}Programmatic Creation
For programmatic creation (without user interaction), use add:
import { createArrow, createText } from "@linkurious/ogma-annotations";
function QuickAddButtons() {
const { add } = useAnnotationsContext();
const addArrow = React.useCallback(() => {
const arrow = createArrow(0, 0, 100, 100, {
strokeColor: "#3498db",
strokeWidth: 2,
head: "arrow"
});
add(arrow);
}, [add]);
const addText = React.useCallback(() => {
const text = createText(50, 50, "Quick Label", {
fontSize: 16,
color: "#2c3e50"
});
add(text);
}, [add]);
return (
<>
<button onClick={addArrow}>Quick Arrow</button>
<button onClick={addText}>Quick Text</button>
</>
);
}Best Practices
1. Use useCallback for Handlers
Prevent unnecessary re-renders by memoizing handlers:
const handleArrow = React.useCallback(() => {
editor.enableArrowDrawing({...});
}, [editor]);2. Clean Up Event Listeners
Always clean up in useEffect:
React.useEffect(() => {
const handler = () => {
/* ... */
};
document.addEventListener("keydown", handler);
return () => document.removeEventListener("keydown", handler);
}, []);3. Provide Visual Feedback
Show users when drawing mode is active:
const [isDrawing, setIsDrawing] = useState(false);
const enableDrawing = () => {
setIsDrawing(true);
editor.enableArrowDrawing({...});
editor.once('completeDrawing', () => setIsDrawing(false));
editor.once('cancelDrawing', () => setIsDrawing(false));
};
return (
<div className={isDrawing ? 'drawing-mode' : ''}>
{isDrawing && <div className="hint">Click to place arrow</div>}
<button onClick={enableDrawing}>Add Arrow</button>
</div>
);Next Steps
- Programmatic Creation - Create annotations in code
- Managing Styles - Build style controls
- Building a Complete Toolbar - Full toolbar example
- Examples - Complete working examples