Skip to content

Commit

Permalink
feat(connector): support offset (#5476)
Browse files Browse the repository at this point in the history
* feat(connector): support offset

* docs: add demo

* fix: ci
  • Loading branch information
pearmini authored Aug 29, 2023
1 parent 73ded25 commit 437b8c3
Show file tree
Hide file tree
Showing 7 changed files with 261 additions and 47 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
107 changes: 107 additions & 0 deletions __tests__/plots/static/alphabet-interval-funnel-connector-label.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { G2Spec } from '../../../src';

export function alphabetIntervalFunnelConnectorLabel(): G2Spec {
const data = [
{ text: 'A', value: 12000 },
{ text: 'B', value: 9800 },
{ text: 'C', value: 6789 },
{ text: 'D', value: 4569 },
];
const encodeX = 'text';
const encodeY = 'value';
const r = (start, end) => `${(((start - end) / start) * 100).toFixed(2)} %`;
return {
type: 'view',
paddingRight: 60,
coordinate: { transform: [{ type: 'transpose' }] },
children: [
{
type: 'interval',
data,
transform: [{ type: 'symmetryY' }],
axis: false,
legend: false,
encode: {
x: encodeX,
y: encodeY,
color: encodeX,
shape: 'funnel',
},
scale: { x: { paddingOuter: 0, paddingInner: 0 } },
labels: [
{
text: (d) => `${d[encodeX]} ${d[encodeY]}`,
position: 'inside',
fontSize: 20,
},
{
text: '',
render: (d, data, i) =>
i !== 0
? `<div style="height:1px;width:30px;background:#aaa;margin:0 5px;"></div>`
: '',
position: 'top-right',
},
{
text: (d, i) => (i !== 0 ? 'Percentage' : ''),
position: 'top-right',
textAlign: 'left',
textBaseline: 'middle',
fill: '#aaa',
dx: 40,
},
{
text: (d, i, data) =>
i !== 0 ? r(data[i - 1][encodeY], data[i][encodeY]) : '',
position: 'top-right',
textAlign: 'left',
textBaseline: 'middle',
dx: 40,
dy: 15,
},
],
},
{
type: 'connector',
data: [
{
startX: data[0][encodeX],
startY: data[data.length - 1][encodeX],
endX: 0,
endY: (data[0][encodeY] - data[data.length - 1][encodeY]) / 2,
},
],
encode: {
x: 'startX',
x1: 'startY',
y: 'endX',
y1: 'endY',
},
labels: [
{
text: 'Percentage',
position: 'left',
textAlign: 'start',
textBaseline: 'middle',
fill: '#aaa',
dx: 10,
},
{
text: r(data[0][encodeY], data[data.length - 1][encodeY]),
position: 'left',
textAlign: 'start',
dy: 15,
dx: 10,
fill: '#000',
},
],
style: {
stroke: '#aaa',
markerEnd: false,
connectLength1: -12,
offset2: -20,
},
},
],
};
}
1 change: 1 addition & 0 deletions __tests__/plots/static/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,3 +280,4 @@ export { cars2PointConstantColorSize } from './cars2-point-constant-color-size';
export { alphabetIntervalTitleAuto } from './alphabet-interval-title-auto';
export { alphabetIntervalAutoPaddingLabelHide } from './alphabet-interval-auto-padding-label-hide';
export { settleWeatherCellLineXY } from './seattle-weather-cell-lineXY';
export { alphabetIntervalFunnelConnectorLabel } from './alphabet-interval-funnel-connector-label';
103 changes: 103 additions & 0 deletions site/examples/general/funnel/demo/funnel-annotation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { Chart } from '@antv/g2';

const r = (start, end) => `${(((start - end) / start) * 100).toFixed(2)} %`;

const data = [
{ text: 'A', value: 12000 },
{ text: 'B', value: 9800 },
{ text: 'C', value: 6789 },
{ text: 'D', value: 4569 },
];
const encodeX = 'text';
const encodeY = 'value';

const chart = new Chart({
container: 'container',
theme: 'classic',
autoFit: true,
paddingRight: 60,
});

chart.coordinate({
transform: [{ type: 'transpose' }],
});

chart
.interval()
.data(data)
.transform({ type: 'symmetryY' })
.axis(false)
.legend(false)
.encode('x', encodeX)
.encode('y', encodeY)
.encode('color', encodeX)
.encode('shape', 'funnel')
.scale('x', { paddingOuter: 0, paddingInner: 0 })
.label({
text: (d) => `${d[encodeX]} ${d[encodeY]}`,
position: 'inside',
fontSize: 20,
})
.label({
text: '',
// Use div to mock a line.
render: (d, data, i) =>
i !== 0
? `<div style="height:1px;width:30px;background:#aaa;margin:0 20px;"></div>`
: '',
position: 'top-right',
})
.label({
text: (d, i) => (i !== 0 ? '转换率' : ''),
position: 'top-right',
textAlign: 'left',
textBaseline: 'middle',
fill: '#aaa',
dx: 60,
})
.label({
text: (d, i, data) =>
i !== 0 ? r(data[i - 1][encodeY], data[i][encodeY]) : '',
position: 'top-right',
textAlign: 'left',
textBaseline: 'middle',
dx: 60,
dy: 15,
});

chart
.connector()
.data([
{
startX: data[0][encodeX],
startY: data[data.length - 1][encodeX],
endX: 0,
endY: (data[0][encodeY] - data[data.length - 1][encodeY]) / 2,
},
])
.encode('x', 'startX')
.encode('x1', 'startY')
.encode('y', 'endX')
.encode('y1', 'endY')
.label({
text: '转换率',
position: 'left',
textAlign: 'start',
textBaseline: 'middle',
fill: '#aaa',
dx: 10,
})
.label({
text: r(data[0][encodeY], data[data.length - 1][encodeY]),
position: 'left',
textAlign: 'start',
dy: 15,
dx: 10,
fill: '#000',
})
.style('stroke', '#aaa')
.style('markerEnd', false)
.style('connectLength1', -12)
.style('offset2', -20);

chart.render();
8 changes: 8 additions & 0 deletions site/examples/general/funnel/demo/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@
"en": "Mirror Funnel"
},
"screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*ejYqRJVJ12gAAAAAAAAAAAAADmJ7AQ/original"
},
{
"filename": "funnel-annotation.ts",
"title": {
"zh": "转化漏斗图",
"en": "Annotation Funnel"
},
"screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*FBnwRpqn-YEAAAAAAAAAAAAADmJ7AQ/original"
}
]
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export {
register,
Runtime,
extend,
type ChartOptions,
} from './api';

export { ChartEvent } from './utils/event';
Expand Down
88 changes: 41 additions & 47 deletions src/shape/connector/connector.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Coordinate } from '@antv/coord';
import type { PathArray } from '@antv/util';
import { PathStyleProps } from '@antv/g';
import { PathStyleProps, Path } from '@antv/g';
import { Marker } from '@antv/gui';
import { line as d3line } from 'd3-shape';
import { ShapeComponent as SC, Vector2, WithPrefix } from '../../runtime';
import { createElement } from '../../utils/createElement';
import { isTranspose } from '../../utils/coordinate';
import { subObject } from '../../utils/helper';
import { select } from '../../utils/selection';
Expand Down Expand Up @@ -33,51 +32,19 @@ function inferConnectorPath(points: Vector2[]) {
.y((d) => d[1])(points);
}

const ConnectorPath = createElement((g) => {
// Do not copy className to path.
const {
points,
class: className,
endMarker = true,
direction,
...rest
} = g.attributes;

const markerStyle = subObject(rest, 'endMarker');
const path = inferConnectorPath(points);

select(g)
.maybeAppend('connector', 'path')
.style('path', path)
.style(
'markerEnd',
endMarker
? new Marker({
className: 'marker',
style: {
...markerStyle,
symbol: inferSymbol,
},
})
: null,
)
.call(applyStyle, rest);
});

function getPoints(
coordinate: Coordinate,
points: Vector2[],
offset: number,
offset1: number,
offset2: number,
length1 = 0,
): Vector2[] {
const [[x0, y0], [x1, y1]] = points;

if (isTranspose(coordinate)) {
const OFFSET = offset;
const X0 = x0 + OFFSET;
const X1 = x1 + OFFSET;
const X0 = x0 + offset1;
const X1 = x1 + offset2;
const X = X0 + length1;

return [
[X0, y0],
[X, y0],
Expand All @@ -86,10 +53,9 @@ function getPoints(
];
}

const OFFSET = -offset;
const Y0 = y0 + OFFSET;
const Y1 = y1 + OFFSET;
const Y = Y0 + -length1;
const Y0 = y0 - offset1;
const Y1 = y1 - offset2;
const Y = Y0 - length1;
return [
[x0, Y0],
[x0, Y],
Expand All @@ -99,17 +65,45 @@ function getPoints(
}

export const Connector: SC<ConnectorOptions> = (options, context) => {
const { offset = 0, connectLength1: length1, ...style } = options;
const {
offset = 0,
offset1 = offset,
offset2 = offset,
connectLength1: length1,
endMarker = true,
...style
} = options;
const { coordinate } = context;

return (points, value, defaults) => {
const { color: defaultColor, connectLength1 = length1, ...rest } = defaults;
const { color: defaultColor, connectLength1, ...rest } = defaults;
const { color, transform } = value;
const P = getPoints(coordinate, points, offset, connectLength1);
return select(new ConnectorPath())
const P = getPoints(
coordinate,
points,
offset1,
offset2,
length1 || connectLength1,
);
const makerStyle = subObject({ ...style, ...defaults }, 'endMarker');

return select(new Path())
.call(applyStyle, rest)
.style('points', P)
.style('path', inferConnectorPath(P))
.style('stroke', color || defaultColor)
.style('transform', transform)
.style(
'markerEnd',
endMarker
? new Marker({
className: 'marker',
style: {
...makerStyle,
symbol: inferSymbol,
},
})
: null,
)
.call(applyStyle, style)
.node();
};
Expand Down

0 comments on commit 437b8c3

Please sign in to comment.