UNPKG

react-native-chart-kit

Version:

Beautiful React Native charts for dashboards, reports, and data-rich mobile apps.

114 lines (113 loc) 4.56 kB
const referenceLabelSeriesPadding = 10; const referenceLabelSpacing = 3; const clamp = (value, min, max) => Math.min(Math.max(value, min), max); const estimateReferenceLabelWidth = (text, fontSize) => Math.max(fontSize, text.length * fontSize * 0.56); const getReferenceLabelBox = ({ fontSize, text, textAnchor, x, y }) => { const width = estimateReferenceLabelWidth(text, fontSize); const left = textAnchor === "middle" ? x - width / 2 : textAnchor === "end" ? x - width : x; return { x: left, y: y - fontSize, width, height: fontSize }; }; const expandBox = (box, padding) => ({ x: box.x - padding, y: box.y - padding, width: box.width + padding * 2, height: box.height + padding * 2 }); const containsPoint = (box, point) => point.x >= box.x && point.x <= box.x + box.width && point.y >= box.y && point.y <= box.y + box.height; const orientation = (a, b, c) => (b.y - a.y) * (c.x - b.x) - (b.x - a.x) * (c.y - b.y); const isBetween = (value, start, end) => value >= Math.min(start, end) && value <= Math.max(start, end); const segmentsIntersect = (a, b, c, d) => { const first = orientation(a, b, c); const second = orientation(a, b, d); const third = orientation(c, d, a); const fourth = orientation(c, d, b); if (first === 0 && isBetween(c.x, a.x, b.x) && isBetween(c.y, a.y, b.y)) { return true; } if (second === 0 && isBetween(d.x, a.x, b.x) && isBetween(d.y, a.y, b.y)) { return true; } if (third === 0 && isBetween(a.x, c.x, d.x) && isBetween(a.y, c.y, d.y)) { return true; } if (fourth === 0 && isBetween(b.x, c.x, d.x) && isBetween(b.y, c.y, d.y)) { return true; } return first > 0 !== second > 0 && third > 0 !== fourth > 0; }; const segmentIntersectsBox = (start, end, box) => { if (containsPoint(box, start) || containsPoint(box, end)) { return true; } const left = box.x; const right = box.x + box.width; const top = box.y; const bottom = box.y + box.height; const topLeft = { x: left, y: top }; const topRight = { x: right, y: top }; const bottomRight = { x: right, y: bottom }; const bottomLeft = { x: left, y: bottom }; return (segmentsIntersect(start, end, topLeft, topRight) || segmentsIntersect(start, end, topRight, bottomRight) || segmentsIntersect(start, end, bottomRight, bottomLeft) || segmentsIntersect(start, end, bottomLeft, topLeft)); }; const collidesWithSeries = (box, geometries) => { if (!geometries || geometries.length === 0) { return false; } const paddedBox = expandBox(box, referenceLabelSeriesPadding); return geometries.some((geometry) => geometry.line.segments.some((segment) => segment.points.some((point, index) => { const next = segment.points[index + 1]; return next ? segmentIntersectsBox(point, next, paddedBox) : false; }))); }; const getReferenceLabelCandidate = ({ geometries, labelOffset, labelPlacement, lineY, plot, text, textAnchor, fontSize, x }) => { const plotBottom = plot.y + plot.height; const desiredY = labelPlacement === "above" ? lineY - labelOffset : lineY + fontSize + labelOffset; const y = clamp(desiredY, plot.y + fontSize, plotBottom - referenceLabelSpacing); const box = getReferenceLabelBox({ fontSize, text, textAnchor, x, y }); const overflow = Math.max(0, plot.y - box.y) + Math.max(0, box.y + box.height - plotBottom); const lineCollision = lineY >= box.y - referenceLabelSpacing && lineY <= box.y + box.height + referenceLabelSpacing; const seriesCollision = collidesWithSeries(box, geometries); return { box, y, score: (labelPlacement === "below" ? 1 : 0) + overflow * 20 + (lineCollision ? 200 : 0) + (seriesCollision ? 100 : 0) }; }; export const resolveReferenceLineLabelY = ({ geometries, labelOffset, labelPlacement, lineY, plot, text, textAnchor, fontSize, x }) => { const placements = labelPlacement === "above" || labelPlacement === "below" ? [labelPlacement] : ["above", "below"]; const candidates = placements.map((placement) => getReferenceLabelCandidate({ geometries, labelOffset, labelPlacement: placement, lineY, plot, text, textAnchor, fontSize, x })); return candidates.sort((a, b) => a.score - b.score)[0]?.y ?? lineY; };