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 @@
+
\ 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 @@
+
\ 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 &&