Text
Interactive text elements can display dynamic values, labels, and annotations that update based on user interactions.
Dynamic Labels
Display values that update as users interact with elements:
import { InteractivePoint } from '@wangyaoshen/locus-interaction';
const point = new InteractivePoint({
position: [100, 100],
onMove: (pos) => {
// Update coordinate display
updateLabel(`(${pos[0].toFixed(0)}, ${pos[1].toFixed(0)})`);
},
});
Text Rendering
Render text on a canvas with proper styling:
function drawText(
ctx: CanvasRenderingContext2D,
text: string,
x: number,
y: number,
options: TextOptions = {}
) {
const {
font = '14px sans-serif',
color = '#333',
align = 'center',
baseline = 'middle',
} = options;
ctx.font = font;
ctx.fillStyle = color;
ctx.textAlign = align;
ctx.textBaseline = baseline;
ctx.fillText(text, x, y);
}
Label Positioning
Position labels relative to interactive elements:
function positionLabel(
elementPos: Vector2,
offset: Vector2 = [0, 20]
): Vector2 {
return [
elementPos[0] + offset[0],
elementPos[1] + offset[1],
];
}
// Usage
const point = new InteractivePoint({
position: [100, 100],
onMove: (pos) => {
const labelPos = positionLabel(pos, [0, 25]);
drawText(ctx, 'Point A', labelPos[0], labelPos[1]);
},
});
Formatted Values
Display formatted numeric values:
// Coordinate format
const formatCoord = (pos: Vector2) =>
`(${pos[0].toFixed(1)}, ${pos[1].toFixed(1)})`;
// Angle format (degrees)
const formatAngle = (radians: number) =>
`${(radians * 180 / Math.PI).toFixed(1)}°`;
// Distance format
const formatDistance = (d: number) =>
`${d.toFixed(1)}px`;
// Percentage format
const formatPercent = (value: number, max: number) =>
`${((value / max) * 100).toFixed(0)}%`;
Text Styles for Themes
Adapt text colors for light/dark themes:
function getTextColors(isDark: boolean) {
return {
primary: isDark ? '#ffffff' : '#333333',
secondary: isDark ? '#999999' : '#666666',
accent: isDark ? '#64B5F6' : '#1976D2',
muted: isDark ? '#555555' : '#bbbbbb',
};
}
// Usage
const colors = getTextColors(isDarkMode);
drawText(ctx, 'Value: 42', x, y, { color: colors.primary });
drawText(ctx, 'units', x + 50, y, { color: colors.secondary });
Annotations
Add explanatory annotations to visualizations:
interface Annotation {
text: string;
position: Vector2;
anchor?: 'left' | 'center' | 'right';
}
function drawAnnotation(
ctx: CanvasRenderingContext2D,
annotation: Annotation
) {
const { text, position, anchor = 'center' } = annotation;
// Background
ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
const metrics = ctx.measureText(text);
const padding = 6;
const width = metrics.width + padding * 2;
const height = 20;
let x = position[0];
if (anchor === 'left') x += width / 2;
if (anchor === 'right') x -= width / 2;
ctx.fillRect(
x - width / 2,
position[1] - height / 2,
width,
height
);
// Text
ctx.fillStyle = '#ffffff';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(text, x, position[1]);
}
Measurement Labels
Show measurements between interactive elements:
function drawMeasurement(
ctx: CanvasRenderingContext2D,
start: Vector2,
end: Vector2
) {
const midX = (start[0] + end[0]) / 2;
const midY = (start[1] + end[1]) / 2;
const distance = Math.sqrt(
Math.pow(end[0] - start[0], 2) +
Math.pow(end[1] - start[1], 2)
);
// Draw measurement line
ctx.setLineDash([4, 4]);
ctx.strokeStyle = '#666';
ctx.beginPath();
ctx.moveTo(start[0], start[1]);
ctx.lineTo(end[0], end[1]);
ctx.stroke();
ctx.setLineDash([]);
// Draw distance label
drawText(ctx, `${distance.toFixed(0)}px`, midX, midY - 10, {
font: '12px monospace',
color: '#666',
});
}
Tips
- Readability: Use sufficient contrast between text and background
- Positioning: Avoid overlapping labels with interactive elements
- Updates: Debounce rapid text updates for performance
- Formatting: Use consistent decimal places and units
- Localization: Consider number formatting for different locales