diff --git a/__tests__/integration/snapshots/static/mockPieSpider.svg b/__tests__/integration/snapshots/static/mockPieSpider.svg new file mode 100644 index 0000000000..123a96b0e4 --- /dev/null +++ b/__tests__/integration/snapshots/static/mockPieSpider.svg @@ -0,0 +1,1504 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 微博 + + + + + + + + + + + + + + + + + + + + 其他 + + + + + + + + + + + + + + + + + + + + 论坛 + + + + + + + + + + + + + + + + + + + + 网站 + + + + + + + + + + + + + + + + + + + + 微信 + + + + + + + + + + + + + + + + + + + + 客户端 + + + + + + + + + + + + + + + + + + + + 新闻 + + + + + + + + + + + + + + + + + + + + 视频 + + + + + + + + + + + + + + + + + + + + 博客 + + + + + + + + + + + + + + + + + + + + 报刊 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 微博 (93.33) + + + + + + + + + + + + + 其他 (6.67) + + + + + + + + + + + + + 论坛 (4.77) + + + + + + + + + + + + + 网站 (1.44) + + + + + + + + + + + + + 微信 (1.12) + + + + + + + + + + + + + 客户端 (1.05) + + + + + + + + + + + + + 新闻 (0.81) + + + + + + + + + + + + + 视频 (0.39) + + + + + + + + + + + + + 博客 (0.37) + + + + + + + + + + + + + 报刊 (0.17) + + + + + + + + + + + + \ No newline at end of file diff --git a/__tests__/integration/snapshots/static/mockPieSpiderRight.svg b/__tests__/integration/snapshots/static/mockPieSpiderRight.svg new file mode 100644 index 0000000000..1fae44a203 --- /dev/null +++ b/__tests__/integration/snapshots/static/mockPieSpiderRight.svg @@ -0,0 +1,1504 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 报刊 + + + + + + + + + + + + + + + + + + + + 博客 + + + + + + + + + + + + + + + + + + + + 视频 + + + + + + + + + + + + + + + + + + + + 新闻 + + + + + + + + + + + + + + + + + + + + 客户端 + + + + + + + + + + + + + + + + + + + + 微信 + + + + + + + + + + + + + + + + + + + + 网站 + + + + + + + + + + + + + + + + + + + + 论坛 + + + + + + + + + + + + + + + + + + + + 其他 + + + + + + + + + + + + + + + + + + + + 微博 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 报刊 (0.17) + + + + + + + + + + + + + 博客 (0.37) + + + + + + + + + + + + + 视频 (0.39) + + + + + + + + + + + + + 新闻 (0.81) + + + + + + + + + + + + + 客户端 (1.05) + + + + + + + + + + + + + 微信 (1.12) + + + + + + + + + + + + + 网站 (1.44) + + + + + + + + + + + + + 论坛 (4.77) + + + + + + + + + + + + + 其他 (6.67) + + + + + + + + + + + + + 微博 (93.33) + + + + + + + + + + + + \ No newline at end of file diff --git a/__tests__/integration/snapshots/static/population2015IntervalSpiderLabel.svg b/__tests__/integration/snapshots/static/population2015IntervalSpiderLabel.svg index 60085e0f3e..6b909b5bc7 100644 --- a/__tests__/integration/snapshots/static/population2015IntervalSpiderLabel.svg +++ b/__tests__/integration/snapshots/static/population2015IntervalSpiderLabel.svg @@ -856,8 +856,8 @@ id="g-svg-97" fill="none" x="36.799999999999955" - y="119.4269445047411" - transform="matrix(1,0,0,1,36.799999,119.426941)" + y="128.38231815400033" + transform="matrix(1,0,0,1,36.799999,128.382324)" class="label" > @@ -890,7 +890,7 @@ @@ -922,6 +922,7 @@ fill="rgba(29,33,41,1)" dominant-baseline="central" paint-order="stroke" + style="transform:translate(0px, 15.198547989487338px);" dx="0.5" fill-opacity="0.65" font-size="12" @@ -935,7 +936,7 @@ @@ -967,6 +968,7 @@ fill="rgba(29,33,41,1)" dominant-baseline="central" paint-order="stroke" + style="transform:translate(0px, 18.280273103385866px);" dx="0.5" fill-opacity="0.65" font-size="12" @@ -980,7 +982,7 @@ @@ -1012,6 +1014,7 @@ fill="rgba(29,33,41,1)" dominant-baseline="central" paint-order="stroke" + style="transform:translate(0px, 11.519948735978517px);" dx="0.5" fill-opacity="0.65" font-size="12" @@ -1025,7 +1028,7 @@ @@ -1070,7 +1073,7 @@ `${obj.type} (${obj.value})`, + labelHeight: 30, + }, + ], + transform: [{ type: 'stackY' }], + coordinate: { type: 'theta' }, + }; +} diff --git a/__tests__/plots/static/mock-pie-spider.ts b/__tests__/plots/static/mock-pie-spider.ts new file mode 100644 index 0000000000..14e518d81e --- /dev/null +++ b/__tests__/plots/static/mock-pie-spider.ts @@ -0,0 +1,31 @@ +import { G2Spec } from '../../../src'; + +export function mockPieSpider(): G2Spec { + return { + type: 'interval', + data: [ + { type: '微博', value: 93.33 }, + { type: '其他', value: 6.67 }, + { type: '论坛', value: 4.77 }, + { type: '网站', value: 1.44 }, + { type: '微信', value: 1.12 }, + { type: '客户端', value: 1.05 }, + { type: '新闻', value: 0.81 }, + { type: '视频', value: 0.39 }, + { type: '博客', value: 0.37 }, + { type: '报刊', value: 0.17 }, + ], + encode: { + y: 'value', + color: 'type', + }, + labels: [ + { + position: 'spider', + text: (obj) => `${obj.type} (${obj.value})`, + }, + ], + transform: [{ type: 'stackY' }], + coordinate: { type: 'theta' }, + }; +} diff --git a/src/shape/label/label.ts b/src/shape/label/label.ts index 861105d5aa..c5d4b38ac2 100644 --- a/src/shape/label/label.ts +++ b/src/shape/label/label.ts @@ -29,6 +29,7 @@ function getDefaultStyle( coordinate: Coordinate, theme: G2Theme, options: LabelOptions, + labels: Vector2[][], ): Record { // For non-series mark, calc position for label based on // position and the bounds of shape. @@ -48,7 +49,7 @@ function getDefaultStyle( } return { ...t, - ...processor(p, points, v, coordinate, options), + ...processor(p, points, v, coordinate, options, labels), }; } @@ -59,7 +60,7 @@ function getDefaultStyle( export const Label: SC = (options, context) => { const { coordinate, theme } = context; const { render } = options; - return (points, value) => { + return (points, value, style, labels) => { const { text, x, @@ -73,7 +74,7 @@ export const Label: SC = (options, context) => { rotate = 0, transform = '', ...defaultStyle - } = getDefaultStyle(points, value, coordinate, theme, options); + } = getDefaultStyle(points, value, coordinate, theme, options, labels); return select(new Advance()) .call(applyStyle, defaultStyle) diff --git a/src/shape/label/position/spider.ts b/src/shape/label/position/spider.ts index b864368e63..af392cb64a 100644 --- a/src/shape/label/position/spider.ts +++ b/src/shape/label/position/spider.ts @@ -1,21 +1,18 @@ import { Coordinate } from '@antv/coord'; +import { sort } from 'd3-array'; import { Vector2 } from '../../../runtime'; import { isCircular } from '../../../utils/coordinate'; import { LabelPosition } from './default'; import { inferOutsideCircularStyle, radiusOf, angleOf } from './outside'; -/** - * Spider label transform only suitable for the labels in polar coordinate, labels should distinguish coordinate type. - */ -export function spider( - position: LabelPosition, +const styleByPoints = new WeakMap(); + +function compute( points: Vector2[], value: Record, coordinate: Coordinate, ) { - if (!isCircular(coordinate)) return {}; const { connectorLength, connectorLength2, connectorDistance } = value; - const { ...style }: any = inferOutsideCircularStyle( 'outside', points, @@ -27,13 +24,50 @@ export function spider( const angle = angleOf(points, value, coordinate); const radius1 = radius + connectorLength + connectorLength2; const sign = Math.sin(angle) > 0 ? 1 : -1; - const newX = center[0] + (radius1 + +connectorDistance) * sign; - const { x: originX } = style; const dx = newX - originX; style.x += dx; style.connectorPoints[0][0] -= dx; - return style; } + +function dodgeY( + styles: Record[], + options: Record = {}, +) { + const { labelHeight = 15 } = options; + const sortedStyles = sort(styles, (d) => d.y); + let preY = -labelHeight; + for (let i = 0; i < sortedStyles.length; ++i) { + const cur = sortedStyles[i]; + const nextY = preY + labelHeight; + const dy = Math.max(0, nextY - cur.y); + cur.labelOffsetY = dy; + preY = cur.y + dy; + } +} + +/** + * Spider label transform only suitable for the labels in polar coordinate, + * labels should distinguish coordinate type. + */ +export function spider( + position: LabelPosition, + points: Vector2[], + value: Record, + coordinate: Coordinate, + options: Record, + labels: Vector2[][], +) { + if (!isCircular(coordinate)) return {}; + if (styleByPoints.has(points)) return styleByPoints.get(points); + const styles = labels.map((points) => compute(points, value, coordinate)); + const { width } = coordinate.getOptions(); + const left = styles.filter((d) => d.x < width / 2); + const right = styles.filter((d) => d.x >= width / 2); + dodgeY(left, options); + dodgeY(right, options); + styles.forEach((style, i) => styleByPoints.set(labels[i], style)); + return styleByPoints.get(points); +} diff --git a/src/shape/text/advance.ts b/src/shape/text/advance.ts index 90f7477210..5ee81571c9 100644 --- a/src/shape/text/advance.ts +++ b/src/shape/text/advance.ts @@ -95,7 +95,9 @@ function inferConnectorPath( shape: DisplayObject, points: Vector2[], controlPoints: Vector2[], - coordCenter, + coordCenter: Vector2, + labelOffset: number, + left = true, ) { const [[x0, y0], [x1, y1]] = points; const [x, y] = getConnectorPoint(shape); @@ -125,6 +127,12 @@ function inferConnectorPath( P.splice(1, 1, [x2, 0]); } + if (labelOffset) { + const prev = P[P.length - 1]; + const prevX = prev[0]; + prev[0] += left ? 4 : -4; + P.push([prevX, labelOffset], [x + (left ? 4 : -4), labelOffset]); + } return line()(P); } @@ -169,6 +177,8 @@ export const Advance = createElement((g) => { let textShape; // Use html to customize advance text. + const labelOffset = rest['labelOffsetY'] || 0; + const dy = (rest.dy || 0) + labelOffset; if (innerHTML) { textShape = select(g) .maybeAppend('html', 'html', className) @@ -178,6 +188,7 @@ export const Advance = createElement((g) => { transform: labelTransform, transformOrigin: labelTransformOrigin, ...rest, + dy, }) .node(); } else { @@ -190,6 +201,7 @@ export const Advance = createElement((g) => { transform: labelTransform, transformOrigin: labelTransformOrigin, ...rest, + dy, }) .node(); } @@ -201,11 +213,14 @@ export const Advance = createElement((g) => { .call(applyStyle, background ? backgroundStyle : {}) .node(); + const left = +x < coordCenter[0]; const connectorPath = inferConnectorPath( rect, endPoints, points, coordCenter, + labelOffset, + left, ); const markerStart = startMarker &&