Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add polyline edge layout for the tree series #7090 #11808

Merged
merged 10 commits into from
Dec 20, 2019
3 changes: 3 additions & 0 deletions src/chart/tree/TreeSeries.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ export default SeriesModel.extend({
// the layout of the tree, two value can be selected, 'orthogonal' or 'radial'
layout: 'orthogonal',

// value can be 'polyline'
edgeLayout: 'curve',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think edgeShape is better. edgeLayout is more like the position of edge

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, good suggestion!


// true | false | 'move' | 'scale', see module:component/helper/RoamController.
roam: false,

Expand Down
201 changes: 182 additions & 19 deletions src/chart/tree/TreeView.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,81 @@ import * as roamHelper from '../../component/helper/roamHelper';
import RoamController from '../../component/helper/RoamController';
import {onIrrelevantElement} from '../../component/helper/cursorHelper';

var TreeShape = graphic.extendShape({
shape: {
parentPoint: [],
childPoints: [],
orient: ''
},

buildPath: function (ctx, shape) {
var ptMin = [Infinity, Infinity];
var ptMax = [-Infinity, -Infinity];
var points = shape.childPoints;
for (var i = 0; i < points.length; i++) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think ptMin ptMax and the switch case of 'TB' 'BT' 'LR' 'RL' and computeMidPoints are not needed.
The code can be simplified if you wish. And some critical case need to be considered. For example:

buildPath: function (ctx, shape) {
    var childPoints = shape.childPoints;
    var parentPoint = shape.parentPoint;
    var childLen = childPoints.length;
    var firstChildPos = childPoints[1];
    var lastChildPos = childPoints[childLen - 1];

    if (!childLen || !parentPoint) {
        return;
    }

    if (childLen === 1) {
        ctx.moveTo(firstChildPos[0], firstChildPos[1]);
        ctx.lineTo(parentPoint[0], parentPoint[1]);
        return;
    }

    var forkDim = (orient === 'TB' || orient === 'BT') ? 1 : 0;
    var otherDim = 1 - forkDim;
    var forkPosition = parsePercent(shape.forkPosition, 1);
    var tmpPt = [];

    ctx.moveTo(parentPoint[0], parentPoint[1]);
    tmpPt[forkDim] = parentPoint[forkDim] + (firstChildPos[forkDim] - parentPoint[forkDim]) * forkPosition;
    tmpPt[otherDim] = parentPoint[otherDim];
    ctx.lineTo(tmpPt[0], tmpPt[1]);

    ctx.moveTo(firstChildPos[0], firstChildPos[1]);
    tmpPt[otherDim] = firstChildPos[otherDim];
    ctx.lineTo(tmpPt[0], tmpPt[1]);
    tmpPt[otherDim] = lastChildPos[otherDim];
    ctx.lineTo(tmpPt[0], tmpPt[1]);
    ctx.lineTo(lastChildPos[0], lastChildPos[1]);

    for (var i = 1; i < childLen - 1; i++) {
        var childPos = childPoints[i];
        ctx.moveTo(childPos[0], childPos[1]);
        tmpPt[otherDim] = childPos[otherDim];
        ctx.lineTo(tmpPt[0], tmpPt[1]);
    }
}

var pt = points[i];
if (pt[0] < ptMin[0]) {
ptMin[0] = pt[0];
}
if (pt[0] > ptMax[0]) {
ptMax[0] = pt[0];
}
if (pt[1] < ptMin[1]) {
ptMin[1] = pt[1];
}
if (pt[1] > ptMax[1]) {
ptMax[1] = pt[1];
}
}
var parentPoint = shape.parentPoint;
var orient = shape.orient;
var midPoint = computeMidPoints(parentPoint, orient, ptMax);

ctx.moveTo(parentPoint[0], parentPoint[1]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong indent

ctx.lineTo(midPoint[0], midPoint[1]);
if (orient === 'TB' || orient === 'BT') {
ctx.moveTo(ptMin[0], midPoint[1]);
ctx.lineTo(ptMax[0], midPoint[1]);
}
else if (orient === 'LR' || orient === 'RL') {
ctx.moveTo(midPoint[0], ptMin[1]);
ctx.lineTo(midPoint[0], ptMax[1]);
}
for (var i = 0; i < points.length; i++) {
var point = points[i];
ctx.moveTo(point[0], point[1]);
if (orient === 'TB' || orient === 'BT') {
ctx.lineTo(point[0], midPoint[1]);
}
else if (orient === 'LR' || orient === 'RL') {
ctx.lineTo(midPoint[0], point[1]);
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Top and bottom horizontal lines are better to be connected with the vertical line.

ctx.moveTo(); // top line end point
ctx.lineTo(); // top line connect point
ctx.lineTo(); // bottom line connect point
ctx.lineTo(); // bottom line end point

It will have a nicer corner than current if the line is thicker.
image

});

function computeMidPoints(parentPoint, orient, ptMax) {
var midPoint = [];
switch (orient) {
case 'TB':
midPoint[0] = parentPoint[0];
midPoint[1] = parentPoint[1] + (ptMax[1] - parentPoint[1]) / 2;
break;
case 'BT':
midPoint[0] = parentPoint[0];
midPoint[1] = ptMax[1] + (parentPoint[1] - ptMax[1]) / 2;
break;
case 'LR':
midPoint[0] = parentPoint[0] + (ptMax[0] - parentPoint[0]) / 2;
midPoint[1] = parentPoint[1];
break;
case 'RL':
midPoint[0] = ptMax[0] + (parentPoint[0] - ptMax[0]) / 2;
midPoint[1] = parentPoint[1];
}
return midPoint;
}

export default echarts.extendChartView({

type: 'tree',
Expand Down Expand Up @@ -87,6 +162,7 @@ export default echarts.extendChartView({
var seriesScope = {
expandAndCollapse: seriesModel.get('expandAndCollapse'),
layout: layout,
edgeLayout: seriesModel.get('edgeLayout'),
orient: seriesModel.getOrient(),
curvature: seriesModel.get('lineStyle.curveness'),
symbolRotate: seriesModel.get('symbolRotate'),
Expand Down Expand Up @@ -388,22 +464,64 @@ function updateNode(data, dataIndex, symbolEl, group, seriesModel, seriesScope)
});
}

if (node.parentNode && node.parentNode !== virtualRoot) {
drawEdge(
seriesModel, node, virtualRoot, symbolEl,
sourceOldLayout, sourceLayout, targetLayout, group, seriesScope
);

}

function drawEdge(
seriesModel, node, virtualRoot, symbolEl, sourceOldLayout, sourceLayout,
targetLayout, group, seriesScope
) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The indent is not good. The back brace should better align with the start of the sentence function,
and the rest of the code in the function should reduce 4 space.

var edgeLayout = seriesScope.edgeLayout;
var edge = symbolEl.__edge;
if (!edge) {
edge = symbolEl.__edge = new graphic.BezierCurve({
shape: getEdgeShape(seriesScope, sourceOldLayout, sourceOldLayout),
style: zrUtil.defaults({opacity: 0, strokeNoScale: true}, seriesScope.lineStyle)
});
}
if (edgeLayout === 'curve') {
if (node.parentNode && node.parentNode !== virtualRoot) {
if (!edge) {
edge = symbolEl.__edge = new graphic.BezierCurve({
shape: getEdgeShape(seriesScope, sourceOldLayout, sourceOldLayout),
style: zrUtil.defaults({opacity: 0, strokeNoScale: true}, seriesScope.lineStyle)
});
}

graphic.updateProps(edge, {
shape: getEdgeShape(seriesScope, sourceLayout, targetLayout),
style: {opacity: 1}
}, seriesModel);
graphic.updateProps(edge, {
shape: getEdgeShape(seriesScope, sourceLayout, targetLayout),
style: {opacity: 1}
}, seriesModel);
}
}
else if (edgeLayout === 'polyline' && seriesScope.layout === 'orthogonal') {
if (node !== virtualRoot && node.children && (node.children.length !== 0) && (node.isExpand === true)) {
var children = node.children;
var childPoints = [];
for (var i = 0; i < children.length; i++) {
var layout = children[i].getLayout();
childPoints.push([layout.x, layout.y]);
}

if (!edge) {
edge = symbolEl.__edge = new TreeShape({
shape: {
parentPoint: [targetLayout.x, targetLayout.y],
childPoints: [[targetLayout.x, targetLayout.y]],
orient: seriesScope.orient
},
style: zrUtil.defaults({opacity: 0, strokeNoScale: true}, seriesScope.lineStyle)
});
}
graphic.updateProps(edge, {
shape: {
parentPoint: [targetLayout.x, targetLayout.y],
childPoints: childPoints,
orient: seriesScope.orient
},
style: {opacity: 1}
}, seriesModel);
}
}
group.add(edge);
}
}

function removeNode(data, dataIndex, symbolEl, group, seriesModel, seriesScope) {
Expand All @@ -413,6 +531,7 @@ function removeNode(data, dataIndex, symbolEl, group, seriesModel, seriesScope)
var seriesScope = getTreeNodeStyle(node, itemModel, seriesScope);

var source = node.parentNode === virtualRoot ? node : node.parentNode || node;
var edgeLayout = seriesScope.edgeLayout;
var sourceLayout;
while (sourceLayout = source.getLayout(), sourceLayout == null) {
source = source.parentNode === virtualRoot ? source : source.parentNode || source;
Expand All @@ -427,16 +546,60 @@ function removeNode(data, dataIndex, symbolEl, group, seriesModel, seriesScope)

symbolEl.fadeOut(null, {keepLabel: true});

var edge = symbolEl.__edge;
var sourceSymbolEl = data.getItemGraphicEl(source.dataIndex);
var sourceEdge = sourceSymbolEl.__edge;
var edge = symbolEl.__edge || (source.isExpand === false ? sourceEdge : undefined);
var edgeLayout = seriesScope.edgeLayout;

if (edge) {
graphic.updateProps(edge, {
shape: getEdgeShape(seriesScope, sourceLayout, sourceLayout),
if (edgeLayout === 'curve') {
graphic.updateProps(edge, {
shape: getEdgeShape(seriesScope, sourceLayout, sourceLayout),
style: {
opacity: 0
}
}, seriesModel, function () {
group.remove(edge);
});
}
else if (edgeLayout === 'polyline' && seriesScope.layout === 'orthogonal') {
graphic.updateProps(edge, {
shape: {
parentPoint: [sourceLayout.x, sourceLayout.y],
childPoints: [[sourceLayout.x, sourceLayout.y]],
orient: seriesScope.orient
},
style: {
opacity: 0
}
}, seriesModel, function () {
group.remove(edge);
});
}
}
else if (sourceEdge) {
var filterChilPoints = [];
var children = source.children;
for (var i = 0; i < children.length; i++) {
if (children[i] !== node) {
var nodeLayout = children[i].getLayout();
filterChilPoints.push([nodeLayout.x, nodeLayout.y]);
}
}
graphic.updateProps(sourceEdge, {
shape: {
parentPoint: [sourceLayout.x, sourceLayout.y],
childPoints: filterChilPoints,
orient: seriesScope.orient
},
style: {
opacity: 0
opacity: 1
}
}, seriesModel, function () {
group.remove(edge);
});
}, seriesModel);

graphic.updateProps(sourceSymbolEl, {
position: [sourceLayout.x, sourceLayout.y]
}, seriesModel);
}
}

Expand Down