Skip to content

Commit

Permalink
WIP(chord): animation
Browse files Browse the repository at this point in the history
  • Loading branch information
Ovilia committed Dec 17, 2024
1 parent 8f8edea commit 668aa13
Show file tree
Hide file tree
Showing 4 changed files with 221 additions and 90 deletions.
141 changes: 137 additions & 4 deletions src/chart/chord/ChordEdge.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { PathProps } from 'zrender/src/graphic/Path';
import PathProxy from 'zrender/src/core/PathProxy';
import type { PathProps } from 'zrender/src/graphic/Path';
import type PathProxy from 'zrender/src/core/PathProxy';
import type { PathStyleProps } from 'zrender/src/graphic/Path';

Check failure on line 3 in src/chart/chord/ChordEdge.ts

View workflow job for this annotation

GitHub Actions / lint (18.x)

'zrender/src/graphic/Path' import is duplicated
import { extend, isString } from 'zrender/src/core/util';
import * as graphic from '../../util/graphic';
import SeriesData from '../../data/SeriesData';
import { GraphEdge } from '../../data/Graph';
import type Model from '../../model/Model';
import { getSectorCornerRadius } from '../helper/sectorHelper';
import { saveOldStyle } from '../../animation/basicTransition';
import ChordSeriesModel, { ChordEdgeItemOption, ChordEdgeLineStyleOption, ChordNodeItemOption } from './ChordSeries';

export class ChordPathShape {
// Souce node, two points forming an arc
Expand Down Expand Up @@ -28,8 +36,15 @@ interface ChordEdgePathProps extends PathProps {
export class ChordEdge extends graphic.Path<ChordEdgePathProps> {
shape: ChordPathShape;

constructor(opts?: ChordEdgePathProps) {
super(opts);
constructor(
nodeData: SeriesData<ChordSeriesModel>,
edgeData: SeriesData,
edgeIdx: number,
startAngle: number
) {
super();

this.updateData(nodeData, edgeData, edgeIdx, startAngle, true);
}

buildPath(ctx: PathProxy | CanvasRenderingContext2D, shape: ChordPathShape): void {
Expand Down Expand Up @@ -66,4 +81,122 @@ export class ChordEdge extends graphic.Path<ChordEdgePathProps> {

ctx.closePath();
}

updateData(
nodeData: SeriesData<ChordSeriesModel>,
edgeData: SeriesData,
edgeIdx: number,
startAngle: number,
firstCreate?: boolean
): void {
const seriesModel = nodeData.hostModel as ChordSeriesModel;
const edge = edgeData.graph.getEdgeByIndex(edgeIdx);
const layout = edge.getLayout();
const itemModel = edge.node1.getModel<ChordNodeItemOption>();
const edgeModel = edgeData.getItemModel<ChordEdgeItemOption>(edge.dataIndex);
const lineStyle = edgeModel.getModel('lineStyle');

const shape: ChordPathShape = extend(
getSectorCornerRadius(itemModel.getModel('itemStyle'), layout, true),
layout
);

const el = this;

// Ignore NaN data.
if (isNaN(shape.sStartAngle) || isNaN(shape.tStartAngle)) {
// Use NaN shape to avoid drawing shape.
el.setShape(shape);
return;
}

if (firstCreate) {
el.setShape(shape);
applyEdgeFill(el, edge, nodeData, lineStyle);

if (startAngle != null) {
const x = shape.cx + shape.r * Math.cos(startAngle);
const y = shape.cy + shape.r * Math.sin(startAngle);
el.setShape({
s1: [x, y],
s2: [x, y],
sStartAngle: startAngle,
sEndAngle: startAngle,
t1: [x, y],
t2: [x, y],
tStartAngle: startAngle,
tEndAngle: startAngle
});
graphic.initProps(el, {
shape
}, seriesModel, edgeIdx);
}
else {
el.setShape({
sEndAngle: el.shape.sStartAngle,
tEndAngle: el.shape.tStartAngle
});
graphic.updateProps(el, {
shape
}, seriesModel, edgeIdx);
}
}
else {
saveOldStyle(el);

applyEdgeFill(el, edge, nodeData, lineStyle);
graphic.updateProps(el, {
shape: shape
}, seriesModel, edgeIdx);
}

edgeData.setItemGraphicEl(edge.dataIndex, el);
}
}

function applyEdgeFill(
edgeShape: ChordEdge,
edge: GraphEdge,
nodeData: SeriesData<ChordSeriesModel>,
lineStyleModel: Model<ChordEdgeLineStyleOption>
) {
const node1 = edge.node1;
const node2 = edge.node2;
const edgeStyle = edgeShape.style as PathStyleProps;

edgeShape.setStyle(lineStyleModel.getLineStyle());

const color = lineStyleModel.get('color');
switch (color) {
case 'source':
// TODO: use visual and node1.getVisual('color');
edgeStyle.fill = nodeData.getItemVisual(node1.dataIndex, 'style').fill;
edgeStyle.decal = node1.getVisual('style').decal;
break;
case 'target':
edgeStyle.fill = nodeData.getItemVisual(node2.dataIndex, 'style').fill;
edgeStyle.decal = node2.getVisual('style').decal;
break;
case 'gradient':
const sourceColor = nodeData.getItemVisual(node1.dataIndex, 'style').fill;
const targetColor = nodeData.getItemVisual(node2.dataIndex, 'style').fill;
if (isString(sourceColor) && isString(targetColor)) {
// Gradient direction is perpendicular to the mid-angles
// of source and target nodes.
const shape = edgeShape.shape;
const sMidX = (shape.s1[0] + shape.s2[0]) / 2;
const sMidY = (shape.s1[1] + shape.s2[1]) / 2;
const tMidX = (shape.t1[0] + shape.t2[0]) / 2;
const tMidY = (shape.t1[1] + shape.t2[1]) / 2;
edgeStyle.fill = new graphic.LinearGradient(
sMidX, sMidY, tMidX, tMidY,
[
{ offset: 0, color: sourceColor },
{ offset: 1, color: targetColor }
],
true
);
}
break;
}
}
37 changes: 36 additions & 1 deletion src/chart/chord/ChordPiece.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,42 @@ export default class ChordPiece extends graphic.Sector {

// layout position is the center of the sector
const layout = data.getItemLayout(idx) as graphic.Sector['shape'];
// console.log(layout)
const shape: graphic.Sector['shape'] = extend(
getSectorCornerRadius(itemModel.getModel('itemStyle'), layout, true),
layout
);

const el = this;

// Ignore NaN data.
if (isNaN(shape.startAngle)) {
// Use NaN shape to avoid drawing shape.
el.setShape(shape);
return;
}

if (firstCreate) {
el.setShape(shape);
console.log(startAngle)

Check failure on line 46 in src/chart/chord/ChordPiece.ts

View workflow job for this annotation

GitHub Actions / lint (18.x)

Unexpected console statement

Check failure on line 46 in src/chart/chord/ChordPiece.ts

View workflow job for this annotation

GitHub Actions / lint (18.x)

Missing semicolon

if (startAngle != null) {
el.setShape({
startAngle,
endAngle: startAngle
});
graphic.initProps(el, {
shape: {
startAngle: shape.startAngle,
endAngle: shape.endAngle
}
}, seriesModel, idx);
}
else {
graphic.updateProps(el, {
shape: shape
}, seriesModel, idx);
}
}

const sectorShape = extend(
getSectorCornerRadius(
Expand Down
1 change: 1 addition & 0 deletions src/chart/chord/ChordSeries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ class ChordSeriesModel extends SeriesModel<ChordSeriesOption> {
},

lineStyle: {
width: 0,
color: 'source',
opacity: 0.5
}
Expand Down
132 changes: 47 additions & 85 deletions src/chart/chord/ChordView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,50 +18,50 @@
*/

import Group from 'zrender/src/graphic/Group';
import type { PathStyleProps } from 'zrender/src/graphic/Path';
import * as graphic from '../../util/graphic';
import ChartView from '../../view/Chart';
import GlobalModel from '../../model/Global';
import ExtensionAPI from '../../core/ExtensionAPI';
import SeriesData from '../../data/SeriesData';
import ChordSeriesModel, { ChordEdgeItemOption, ChordEdgeLineStyleOption, ChordNodeItemOption } from './ChordSeries';
import ChordSeriesModel from './ChordSeries';
import ChordPiece from './ChordPiece';
import { ChordEdge } from './ChordEdge';
import type { GraphEdge } from '../../data/Graph';
import { isString } from 'zrender/src/core/util';
import { normalizeArcAngles } from 'zrender/src/core/PathProxy';

Check warning on line 29 in src/chart/chord/ChordView.ts

View workflow job for this annotation

GitHub Actions / lint (18.x)

'normalizeArcAngles' is defined but never used

const PI2 = Math.PI * 2;

Check warning on line 31 in src/chart/chord/ChordView.ts

View workflow job for this annotation

GitHub Actions / lint (18.x)

'PI2' is assigned a value but never used
const RADIAN = Math.PI / 180;

class ChordView extends ChartView {

static readonly type = 'chord';
readonly type: string = ChordView.type;

private _data: SeriesData;
private _edgeData: SeriesData;
private _edgeGroup: Group;

init(ecModel: GlobalModel, api: ExtensionAPI) {
}

render(seriesModel: ChordSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) {
const data = seriesModel.getData();

const oldData = this._data;
const group = this.group;

const startAngle = -seriesModel.get('startAngle') * RADIAN;

data.diff(oldData)
.add(function (newIdx) {
const el = new ChordPiece(data, newIdx, 90);
const el = new ChordPiece(data, newIdx, startAngle);
data.setItemGraphicEl(newIdx, el);
group.add(el);
})

.update(function (newIdx, oldIdx) {
let el = oldData.getItemGraphicEl(oldIdx) as ChordPiece;
if (!el) {
el = new ChordPiece(data, newIdx, 90);
}

el.updateData(data, newIdx);
group.add(el);
const el = oldData.getItemGraphicEl(oldIdx) as ChordPiece;
el.updateData(data, newIdx, startAngle);
data.setItemGraphicEl(newIdx, el);
group.add(el);
})

.remove(function (oldIdx) {
Expand All @@ -71,90 +71,52 @@ class ChordView extends ChartView {

.execute();

this.renderEdges(seriesModel);

this._data = data;

this.renderEdges(seriesModel, startAngle);
}

renderEdges(seriesModel: ChordSeriesModel) {
const edgeGroup = new Group();
this.group.add(edgeGroup);
renderEdges(seriesModel: ChordSeriesModel, startAngle: number) {
if (!this._edgeGroup) {
this._edgeGroup = new Group();
this.group.add(this._edgeGroup);
}

const nodeData = seriesModel.getData();
const nodeGraph = nodeData.graph;
const edgeGroup = this._edgeGroup;
edgeGroup.removeAll();

const nodeData = seriesModel.getData();
const edgeData = seriesModel.getEdgeData();
nodeGraph.eachEdge(function (edge) {
const layout = edge.getLayout();
const itemModel = edge.node1.getModel<ChordNodeItemOption>();
const borderRadius = itemModel.get(['itemStyle', 'borderRadius']);
const edgeModel = edgeData.getItemModel<ChordEdgeItemOption>(edge.dataIndex);
const lineStyle = edgeModel.getModel('lineStyle');
const lineColor = lineStyle.get('color');

let el = edgeData.getItemGraphicEl(edge.dataIndex) as ChordEdge;
if (!el) {
el = new ChordEdge({
shape: {
r: borderRadius,
...layout
},
style: lineStyle.getItemStyle()
});
}
applyEdgeFill(el, edge, nodeData, lineColor);
edgeGroup.add(el);

edgeData.setItemGraphicEl(edge.dataIndex, el);
});
const oldData = this._edgeData;

edgeData.diff(oldData)
.add(function (newIdx) {
const el = new ChordEdge(nodeData, edgeData, newIdx, startAngle);
edgeData.setItemGraphicEl(newIdx, el);
edgeGroup.add(el);
})

.update(function (newIdx, oldIdx) {
const el = oldData.getItemGraphicEl(oldIdx) as ChordEdge;
el.updateData(nodeData, edgeData, newIdx, startAngle);
edgeData.setItemGraphicEl(newIdx, el);
edgeGroup.add(el);
})

.remove(function (oldIdx) {
const el = oldData.getItemGraphicEl(oldIdx) as ChordEdge;
el && graphic.removeElementWithFadeOut(el, seriesModel, oldIdx);
})

.execute();

this._edgeData = edgeData;
}

dispose() {

}
}

function applyEdgeFill(
edgeShape: ChordEdge,
edge: GraphEdge,
nodeData: SeriesData<ChordSeriesModel>,
color: ChordEdgeLineStyleOption['color']
) {
const node1 = edge.node1;
const node2 = edge.node2;
const edgeStyle = edgeShape.style as PathStyleProps;
switch (color) {
case 'source':
// TODO: use visual and node1.getVisual('color');
edgeStyle.fill = nodeData.getItemVisual(edge.node1.dataIndex, 'style').fill;
edgeStyle.decal = edge.node1.getVisual('style').decal;
break;
case 'target':
edgeStyle.fill = nodeData.getItemVisual(edge.node2.dataIndex, 'style').fill;
edgeStyle.decal = edge.node2.getVisual('style').decal;
break;
case 'gradient':
const sourceColor = nodeData.getItemVisual(edge.node1.dataIndex, 'style').fill;
const targetColor = nodeData.getItemVisual(edge.node2.dataIndex, 'style').fill;
if (isString(sourceColor) && isString(targetColor)) {
// Gradient direction is perpendicular to the mid-angles
// of source and target nodes.
const shape = edgeShape.shape;
const sMidX = (shape.s1[0] + shape.s2[0]) / 2;
const sMidY = (shape.s1[1] + shape.s2[1]) / 2;
const tMidX = (shape.t1[0] + shape.t2[0]) / 2;
const tMidY = (shape.t1[1] + shape.t2[1]) / 2;
edgeStyle.fill = new graphic.LinearGradient(
sMidX, sMidY, tMidX, tMidY,
[
{ offset: 0, color: sourceColor },
{ offset: 1, color: targetColor }
],
true
);
}
break;
}
}

export default ChordView;

0 comments on commit 668aa13

Please sign in to comment.