UNPKG

@tanstack/charts

Version:

A chart grammar for TypeScript and JavaScript. Marks consume your data directly, channels describe visual encodings, and the engine compiles them into a renderer-neutral keyed scene. TanStack's compact scales cover common numeric and categorical mappings.

61 lines (60 loc) 2.32 kB
const epsilon = 1e-6; function dodgeOffsets(measuredPositions, radii, padding, edgeAnchored) { if (measuredPositions.length !== radii.length) { throw new TypeError("dodge: positions and radii must have equal lengths"); } const offsets = Array.from({ length: measuredPositions.length }, () => 0); const order = measuredPositions.map((_position, index) => index); if (hasVariableRadius(radii)) { order.sort((left, right) => radii[right] - radii[left] || left - right); } const placed = []; for (const index of order) { const measured = measuredPositions[index]; const radius = radii[index]; const baseline = edgeAnchored ? radius + padding : 0; const forbidden = []; for (const otherIndex of placed) { const distance = radius + radii[otherIndex] + padding; const measuredDelta = measured - measuredPositions[otherIndex]; if (Math.abs(measuredDelta) > distance) continue; const crossDelta = Math.sqrt( Math.max(0, distance * distance - measuredDelta * measuredDelta) ); forbidden.push({ minimum: offsets[otherIndex] - crossDelta, maximum: offsets[otherIndex] + crossDelta }); } const candidates = [{ value: baseline, order: 0 }]; forbidden.forEach((interval, intervalIndex) => { candidates.push( { value: interval.minimum, order: intervalIndex * 2 + 1 }, { value: interval.maximum, order: intervalIndex * 2 + 2 } ); }); candidates.sort((left, right) => { const leftRank = edgeAnchored ? left.value : Math.abs(left.value); const rightRank = edgeAnchored ? right.value : Math.abs(right.value); return leftRank - rightRank || left.order - right.order; }); const selected = candidates.find( (candidate) => (!edgeAnchored || candidate.value >= baseline - epsilon) && forbidden.every( (interval) => candidate.value <= interval.minimum + epsilon || candidate.value >= interval.maximum - epsilon ) ); if (!selected) { throw new TypeError("dodge: could not place a finite circle"); } offsets[index] = selected.value; placed.push(index); } return offsets; } function hasVariableRadius(radii) { const first = radii[0]; return radii.some((radius) => radius !== first); } export { dodgeOffsets };