Skip to content

Commit

Permalink
feat(line): add trail shape (#5137)
Browse files Browse the repository at this point in the history
  • Loading branch information
pearmini committed Jun 1, 2023
1 parent 534c1ef commit 34937ea
Show file tree
Hide file tree
Showing 15 changed files with 261 additions and 18 deletions.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
45 changes: 45 additions & 0 deletions __tests__/plots/static/barley-line-trail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { csv } from 'd3-fetch';
import { autoType } from 'd3-dsv';
import { rollup } from 'd3-array';
import { G2Spec } from '../../../src';

export async function barleyLineTrail(): Promise<G2Spec> {
const data = await csv('data/barley.csv', autoType);
const key = (d) => `${d.site},${d.variety}`;
const keyDelta = rollup(
data,
([a, b]) => {
if (b.year < a.year) [a, b] = [b, a];
return b.yield - a.yield;
},
key,
);
return {
type: 'facetRect',
data,
paddingLeft: 120,
paddingBottom: 100,
encode: { x: 'site' },
children: [
{
type: 'line',
frame: false,
encode: {
x: (d) => `${d.year}`,
y: 'variety',
series: 'variety',
color: (d) => keyDelta.get(key(d)),
size: 'yield',
},
tooltip: { title: '', items: [{ field: 'year' }, { field: 'yield' }] },
legend: { size: false, color: { title: 'yield delta' } },
scale: {
size: { range: [0, 12] },
color: { palette: 'rdBu' },
},
style: { shape: 'trail' },
interaction: { tooltip: { series: false } },
},
],
};
}
2 changes: 2 additions & 0 deletions __tests__/plots/static/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,3 +238,5 @@ export { basicLineNullLabel } from './basic-line-null-label';
export { alphabetIntervalViewStyle } from './alphabet-interval-view-style';
export { vennBasic } from './venn-basic';
export { vennHollow } from './venn-hollow';
export { stocksLineVarSize } from './stocks-line-var-size';
export { barleyLineTrail } from './barley-line-trail';
19 changes: 19 additions & 0 deletions __tests__/plots/static/stocks-line-var-size.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { G2Spec } from '../../../src';

export function stocksLineVarSize(): G2Spec {
return {
type: 'line',
data: {
type: 'fetch',
value: 'data/stocks.csv',
},
encode: {
x: (d) => new Date(d.date),
y: 'price',
color: 'symbol',
size: 'price',
},
legend: { size: false },
style: { shape: 'trail' },
};
}
2 changes: 2 additions & 0 deletions __tests__/unit/stdlib/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ import {
Density as DensityShape,
Heatmap as HeatmapShape,
Shape as CustomShape,
Trail,
} from '../../../src/shape';
import { Classic, ClassicDark, Academy } from '../../../src/theme';
import {
Expand Down Expand Up @@ -404,6 +405,7 @@ describe('stdlib', () => {
'shape.line.vh': VH,
'shape.line.hvh': HVH,
'shape.line.smooth': Smooth,
'shape.line.trail': Trail,
'shape.point.bowtie': Bowtie,
'shape.point.cross': Cross,
'shape.point.diamond': Diamond,
Expand Down
3 changes: 2 additions & 1 deletion site/docs/spec/mark/line.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,13 @@ chart.render();
| vh | 绘制阶梯折线图,先竖线后横线连接 | |
| hv | 绘制阶梯折线图,先横线后竖线连接 | |
| hvh | 绘制阶梯折线图,竖横竖,中点连接 | |
| trail | 绘制轨迹,类似一个笔迹 | |

### line

| 属性 | 描述 | 类型 | 默认值 |
| -------------- | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | ------------------------------ |
| connect | 是否连接空值 | `number` \| `Function<number>` | false |
| connect | 是否连接空值 | `number` \| `Function<number>` | false |
| connect[Style] | connector 对应的属性样式 | 和对应 `style` 保持一致 | - |
| defined | 决定数据是否为空值 | `(v: any) = boolean` | !(NaN \|\| null \|\| undefine) |
| fill | 图形的填充色 | `string` \| `Function<string>` | - |
Expand Down
45 changes: 45 additions & 0 deletions site/examples/general/line/demo/line-var-size-facet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* A recreation of this demo: https://vega.github.io/vega-lite/examples/trail_comet.html
*/
import { Chart } from '@antv/g2';
import { rollup } from 'd3-array';

fetch('https://assets.antv.antgroup.com/g2/barley.json')
.then((res) => res.json())
.then((data) => {
const key = (d) => `${d.site},${d.variety}`;
const keyDelta = rollup(
data,
([a, b]) => {
if (b.year < a.year) [a, b] = [b, a];
return b.yield - a.yield;
},
key,
);

const chart = new Chart({
container: 'container',
theme: 'classic',
paddingLeft: 120,
paddingBottom: 100,
});

const facet = chart.facetRect().data(data).encode('x', 'site');

facet
.line()
.encode('x', (d) => `${d.year}`)
.encode('y', 'variety')
.encode('series', 'variety')
.encode('color', (d) => keyDelta.get(key(d)))
.encode('size', 'yield')
.tooltip({ title: '', items: [{ field: 'year' }, { field: 'yield' }] })
.scale('size', { range: [0, 12] })
.scale('color', { palette: 'rdBu' })
.style('shape', 'trail')
.legend('color', { title: 'yield delta' })
.attr('frame', false)
.interaction('tooltip', { series: false });

chart.render();
});
26 changes: 26 additions & 0 deletions site/examples/general/line/demo/line-var-size.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* A recreation of this demo: https://vega.github.io/vega-lite/examples/trail_color.html
*/
import { Chart } from '@antv/g2';

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

chart
.line()
.data({
type: 'fetch',
value:
'https://gw.alipayobjects.com/os/bmw-prod/cb99c4ab-e0a3-4c76-9586-fe7fa2ff1a8c.csv',
})
.encode('x', (d) => new Date(d.date))
.encode('y', 'price')
.encode('color', 'symbol')
.encode('size', 'price')
.legend('size', false)
.style('shape', 'trail');

chart.render();
16 changes: 16 additions & 0 deletions site/examples/general/line/demo/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,22 @@
},
"screenshot": "https://mdn.alipayobjects.com/mdn/huamei_qa8qxu/afts/img/A*-q4yQZ7WtDcAAAAAAAAAAAAADmJ7AQ"
},
{
"filename": "line-var-size.ts",
"title": {
"zh": "变宽折线图",
"en": "Var Size Line Chart"
},
"screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*pWrERLZIMyQAAAAAAAAAAAAADmJ7AQ/original"
},
{
"filename": "line-var-size-facet.ts",
"title": {
"zh": "轨迹图",
"en": "Trail"
},
"screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*yI3VRr4tw44AAAAAAAAAAAAADmJ7AQ/original"
},
{
"filename": "line-variable-color.ts",
"title": {
Expand Down
31 changes: 14 additions & 17 deletions src/composition/facetRect.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { deepMix } from '@antv/util';
import { group, max } from 'd3-array';
import { extent, group, max } from 'd3-array';
import {
CompositionComponent as CC,
G2MarkChildrenCallback,
Expand Down Expand Up @@ -79,24 +79,21 @@ export const inferColor = useDefaultAdaptor<G2ViewTree>(

const domainColor = () => {
const domain = scale?.color?.domain;
if (domain !== undefined) return domain;
if (encodeColor === undefined) return undefined;
return Array.from(new Set(data.map((d) => d[encodeColor])));
if (domain !== undefined) return [domain];
if (encodeColor === undefined) return [undefined];
const color =
typeof encodeColor === 'function' ? encodeColor : (d) => d[encodeColor];
const values = data.map(color);
if (values.some((d) => typeof d === 'number')) return [extent(values)];
return [Array.from(new Set(values)), 'ordinal'];
};

const title = typeof encodeColor === 'string' ? encodeColor : '';
const [domain, type] = domainColor();
return {
encode: {
color: encodeColor,
},
scale: {
color: deepMix({}, scaleColor, {
domain: domainColor(),
// @todo Remove this when pass columnOf to extract color.
type: 'ordinal',
}),
},
legend: {
color: deepMix({ title: encodeColor }, legendColor),
},
encode: { color: encodeColor },
scale: { color: deepMix({}, scaleColor, { domain, type }) },
legend: { color: deepMix({ title }, legendColor) },
};
},
);
Expand Down
4 changes: 4 additions & 0 deletions src/shape/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ export { Rect } from './interval/rect';
export { Hollow as HollowRect } from './interval/hollow';
export { Funnel } from './interval/funnel';
export { Pyramid } from './interval/pyramid';

export { Line } from './line/line';
export { Smooth } from './line/smooth';
export { HV } from './line/hv';
export { VH } from './line/vh';
export { HVH } from './line/hvh';
export { Trail } from './line/trail';

export { Bowtie } from './point/bowtie';
export { Cross } from './point/cross';
export { Diamond } from './point/diamond';
Expand Down Expand Up @@ -61,6 +64,7 @@ export type { SmoothOptions } from './line/smooth';
export type { HVOptions } from './line/hv';
export type { VHOptions } from './line/vh';
export type { HVHOptions } from './line/hvh';
export type { TrailOptions } from './line/trail';

export type { BowtieOptions } from './point/bowtie';
export type { CrossOptions } from './point/cross';
Expand Down
80 changes: 80 additions & 0 deletions src/shape/line/trail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { path as d3path } from 'd3-path';
import { Path } from '@antv/g';
import { ShapeComponent as SC } from '../../runtime';
import { select } from '../../utils/selection';
import { applyStyle, getShapeTheme } from '../utils';
import { angle, dist, sub, add, Vector2 } from '../../utils/vector';
import { Curve } from './curve';

/**
*
* x9-x0---------x1-x2
* / |r1 |r2 \
*x8---p0---------p1---x3
* \ r4| | r3 /
* x7-x6--------x5-x4
*/
function stroke(path, p0, p1, s0, s1) {
const v = sub(p1, p0);
const a = angle(v);
const a1 = a + Math.PI / 2;
const r1: Vector2 = [(s0 / 2) * Math.cos(a1), (s0 / 2) * Math.sin(a1)];
const r2: Vector2 = [(s1 / 2) * Math.cos(a1), (s1 / 2) * Math.sin(a1)];
const r3: Vector2 = [(s1 / 2) * Math.cos(a), (s1 / 2) * Math.sin(a)];
const r4: Vector2 = [(s0 / 2) * Math.cos(a), (s0 / 2) * Math.sin(a)];
const x0 = add(p0, r1);
const x1 = add(p1, r2);
const x2 = add(x1, r3);
const x3 = add(p1, r3);
const x4 = sub(x3, r2);
const x5 = sub(p1, r2);
const x6 = sub(p0, r1);
const x7 = sub(x6, r4);
const x8 = sub(p0, r4);
const x9 = sub(x0, r4);

path.moveTo(...x0);
path.lineTo(...x1);
path.arcTo(...x2, ...x3, s1 / 2);
path.arcTo(...x4, ...x5, s1 / 2);
path.lineTo(...x6);
path.arcTo(...x7, ...x8, s0 / 2);
path.arcTo(...x9, ...x0, s0 / 2);
path.closePath();
}

export type TrailOptions = Record<string, any>;

export const Trail: SC<TrailOptions> = (options) => {
return (P, value, coordinate, theme) => {
const { mark, shape, defaultShape, seriesSize, color } = value;
const { defaultColor, ...defaults } = getShapeTheme(
theme,
mark,
shape,
defaultShape,
);

const path = d3path();

for (let i = 0; i < P.length - 1; i++) {
const p0 = P[i];
const p1 = P[i + 1];
const s0 = seriesSize[i];
const s1 = seriesSize[i + 1];
stroke(path, p0, p1, s0, s1);
}

return select(new Path({}))
.call(applyStyle, defaults)
.style('fill', color || defaultColor)
.style('d', path.toString())
.call(applyStyle, options)
.node();
};
};

Trail.props = {
...Curve.props,
defaultMarker: 'line',
};
2 changes: 2 additions & 0 deletions src/stdlib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ import {
Density as DensityShape,
Shape as CustomShape,
Heatmap as HeatmapShape,
Trail,
} from '../shape';
import { Classic, ClassicDark, Academy } from '../theme';
import {
Expand Down Expand Up @@ -389,6 +390,7 @@ export function createLibrary(): G2Library {
'shape.line.hv': HV,
'shape.line.vh': VH,
'shape.line.hvh': HVH,
'shape.line.trail': Trail,
'shape.line.smooth': Smooth,
'shape.point.bowtie': Bowtie,
'shape.point.cross': Cross,
Expand Down
4 changes: 4 additions & 0 deletions src/utils/vector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ export function sub([x1, y1]: Vector2, [x2, y2]: Vector2): Vector2 {
return [x1 - x2, y1 - y2];
}

export function add([x1, y1]: Vector2, [x2, y2]: Vector2): Vector2 {
return [x1 + x2, y1 + y2];
}

export function dist([x0, y0]: Vector2, [x1, y1]: Vector2): number {
return Math.sqrt((x0 - x1) ** 2 + (y0 - y1) ** 2);
}
Expand Down

0 comments on commit 34937ea

Please sign in to comment.