Skip to main content

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

  1. Readability: Use sufficient contrast between text and background
  2. Positioning: Avoid overlapping labels with interactive elements
  3. Updates: Debounce rapid text updates for performance
  4. Formatting: Use consistent decimal places and units
  5. Localization: Consider number formatting for different locales