UNPKG

@antv/g2

Version:

the Grammar of Graphics in Javascript

268 lines (241 loc) 6.93 kB
import { Vector2 } from '@antv/coord'; import { DisplayObject, Text as GText, Rect, TextStyleProps, RectStyleProps, PathStyleProps, } from '@antv/g'; import { isNumber } from '@antv/util'; import { Marker } from '@antv/component'; import { line } from '@antv/vendor/d3-shape'; import { WithPrefix } from '../../runtime'; import { createElement } from '../../utils/createElement'; import { applyStyle } from '../utils'; import { subObject } from '../../utils/helper'; import { select } from '../../utils/selection'; import { dist } from '../../utils/vector'; type BackgroundStyleProps = WithPrefix< RectStyleProps & { padding?: number[]; radius?: number }, 'background' >; type ConnectorStyleProps = WithPrefix< PathStyleProps & { points?: Vector2[] }, 'connector' >; type MarkerStyleProps<P extends string> = WithPrefix<Record<string, any>, P>; type TextShapeStyleProps = Omit<TextStyleProps, 'text'> & ConnectorStyleProps & BackgroundStyleProps & MarkerStyleProps<'startMarker'> & MarkerStyleProps<'endMarker'> & { id: string; className?: string; x0?: number; // x0 represents the x position of relative point, default is equal to x y0?: number; coordCenter?: Vector2; // center of coordinate background?: boolean; connector?: boolean; startMarker?: boolean; endMarker?: boolean; labelTransform?: string; labelTransformOrigin?: string; rotate?: number; innerHTML?: string | HTMLElement; text?: string; }; function getConnectorPoint(shape: GText | Rect): [number, number] { const { min: [x0, y0], max: [x1, y1], } = shape.getLocalBounds(); let x = 0; let y = 0; if (x0 > 0) x = x0; if (x1 < 0) x = x1; if (y0 > 0) y = y0; if (y1 < 0) y = y1; return [x, y]; } function inferBackgroundBounds(textShape: DisplayObject, padding = []) { const [top = 0, right = 0, bottom = top, left = right] = padding; const container = textShape.parentNode as DisplayObject; const angle = container.getEulerAngles(); container.setEulerAngles(0); const { min, halfExtents } = textShape.getLocalBounds(); const [x, y] = min; const [hw, hh] = halfExtents; container.setEulerAngles(angle); return { x: x - left, y: y - top, width: hw * 2 + left + right, height: hh * 2 + top + bottom, }; } const cos = (p0: Vector2, p1: Vector2, p2: Vector2) => { const a = dist(p0, p1); const b = dist(p1, p2); const c = dist(p2, p0); return (a ** 2 + b ** 2 - c ** 2) / (2 * a * b); }; // A path from element to label. // Adapted drawLabelLine from https://github.com/antvis/G2/blob/master/src/geometry/label/layout/pie/spider.ts function inferConnectorPath( shape: DisplayObject, end: Vector2, control: Vector2[], coordCenter: Vector2, left = true, top = true, ) { const path = (points) => line()(points); if (!end[0] && !end[1]) return path([getConnectorPoint(shape), end]); if (!control.length) return path([[0, 0], end]); const [inflection, start] = control; const p1 = [...start]; const p2 = [...inflection]; // Label has been adjusted, so add offset to the label. if (start[0] !== inflection[0]) { const offset = left ? -4 : 4; p1[1] = start[1]; // For the label in the first quadrant. if (top && !left) { p1[0] = Math.max(inflection[0], start[0] - offset); if (start[1] < inflection[1]) { p2[1] = p1[1]; } else { p2[1] = inflection[1]; p2[0] = Math.max(p2[0], p1[0] - offset); } } // For the label in the second quadrant. if (!top && !left) { p1[0] = Math.max(inflection[0], start[0] - offset); if (start[1] > inflection[1]) { p2[1] = p1[1]; } else { p2[1] = inflection[1]; p2[0] = Math.max(p2[0], p1[0] - offset); } } // For the label in the third quadrant. if (!top && left) { p1[0] = Math.min(inflection[0], start[0] - offset); if (start[1] > inflection[1]) { p2[1] = p1[1]; } else { p2[1] = inflection[1]; p2[0] = Math.min(p2[0], p1[0] - offset); } } // For the label in the fourth quadrant. if (top && left) { p1[0] = Math.min(inflection[0], start[0] - offset); if (start[1] < inflection[1]) { p2[1] = p1[1]; } else { p2[1] = inflection[1]; p2[0] = Math.min(p2[0], p1[0] - offset); } } } return path([start, p1, p2, inflection, end]); } export const Advance = createElement((g) => { const { className, // Do not pass className class: _c, transform, rotate, labelTransform, labelTransformOrigin, x, y, x0 = x, y0 = y, text, background, connector, startMarker, endMarker, coordCenter, innerHTML, ...rest } = g.attributes as TextShapeStyleProps; g.style.transform = `translate(${x}, ${y})`; // Position is invalid, do not render the UI, // or clear previous elements. if ([x, y, x0, y0].some((v) => !isNumber(v))) { g.children.forEach((d) => d.remove()); return; } const { padding, ...backgroundStyle } = subObject(rest, 'background'); const { points: controlPoints = [], ...connectorStyle } = subObject( rest, 'connector', ); let textShape; if (innerHTML) { textShape = select(g) .maybeAppend('html', 'html', className) .style('zIndex', 0) .style('innerHTML', innerHTML) .call(applyStyle, { transform: labelTransform, transformOrigin: labelTransformOrigin, ...rest, }) .node(); } else { textShape = select(g) .maybeAppend('text', 'text') .style('zIndex', 0) .style('text', text) .call(applyStyle, { textBaseline: 'middle', transform: labelTransform, transformOrigin: labelTransformOrigin, ...rest, }) .node(); } const rect = select(g) .maybeAppend('background', 'rect') .style('zIndex', -1) .call(applyStyle, inferBackgroundBounds(textShape, padding)) .call(applyStyle, background ? backgroundStyle : {}) .node(); const left = +x0 < coordCenter[0]; const top = +y0 < coordCenter[1]; const end: [number, number] = [+x0 - +x, +y0 - +y]; const connectorPath = inferConnectorPath( rect, end, controlPoints, coordCenter, left, top, ); const markerStart = startMarker && new Marker({ id: 'startMarker', style: { x: 0, y: 0, ...(subObject(rest, 'startMarker') as any) }, }); const markerEnd = endMarker && new Marker({ id: 'endMarker', style: { x: 0, y: 0, ...(subObject(rest, 'endMarker') as any) }, }); select(g) .maybeAppend('connector', 'path') .style('zIndex', 0) .style('d', connectorPath) .style('markerStart', markerStart) .style('markerEnd', markerEnd) .call(applyStyle, connector ? connectorStyle : {}); });