blob: a78249919aa7f1868ebc905f827b763354e4ea9b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* @file The file used to draw sankey diagram.
* @author Deqing Li(annong035@gmail.com)
*/
import * as graphic from '../../util/graphic';
import * as echarts from '../../echarts';
import * as zrUtil from 'zrender/src/core/util';
var nodeOpacityPath = ['itemStyle', 'opacity'];
var lineOpacityPath = ['lineStyle', 'opacity'];
function getItemOpacity(item, opacityPath) {
return item.getVisual('opacity') || item.getModel().get(opacityPath);
}
function fadeOutItem(item, opacityPath, opacityRatio) {
var el = item.getGraphicEl();
var opacity = getItemOpacity(item, opacityPath);
if (opacityRatio != null) {
opacity == null && (opacity = 1);
opacity *= opacityRatio;
}
el.downplay && el.downplay();
el.traverse(function (child) {
if (child.type !== 'group') {
child.setStyle('opacity', opacity);
}
});
}
function fadeInItem(item, opacityPath) {
var opacity = getItemOpacity(item, opacityPath);
var el = item.getGraphicEl();
el.highlight && el.highlight();
el.traverse(function (child) {
if (child.type !== 'group') {
child.setStyle('opacity', opacity);
}
});
}
var SankeyShape = graphic.extendShape({
shape: {
x1: 0,
y1: 0,
x2: 0,
y2: 0,
cpx1: 0,
cpy1: 0,
cpx2: 0,
cpy2: 0,
extent: 0,
orient: ''
},
buildPath: function (ctx, shape) {
var extent = shape.extent;
ctx.moveTo(shape.x1, shape.y1);
ctx.bezierCurveTo(shape.cpx1, shape.cpy1, shape.cpx2, shape.cpy2, shape.x2, shape.y2);
if (shape.orient === 'vertical') {
ctx.lineTo(shape.x2 + extent, shape.y2);
ctx.bezierCurveTo(shape.cpx2 + extent, shape.cpy2, shape.cpx1 + extent, shape.cpy1, shape.x1 + extent, shape.y1);
} else {
ctx.lineTo(shape.x2, shape.y2 + extent);
ctx.bezierCurveTo(shape.cpx2, shape.cpy2 + extent, shape.cpx1, shape.cpy1 + extent, shape.x1, shape.y1 + extent);
}
ctx.closePath();
}
});
export default echarts.extendChartView({
type: 'sankey',
/**
* @private
* @type {module:echarts/chart/sankey/SankeySeries}
*/
_model: null,
/**
* @private
* @type {boolean}
*/
_focusAdjacencyDisabled: false,
render: function (seriesModel, ecModel, api) {
var sankeyView = this;
var graph = seriesModel.getGraph();
var group = this.group;
var layoutInfo = seriesModel.layoutInfo; // view width
var width = layoutInfo.width; // view height
var height = layoutInfo.height;
var nodeData = seriesModel.getData();
var edgeData = seriesModel.getData('edge');
var orient = seriesModel.get('orient');
this._model = seriesModel;
group.removeAll();
group.attr('position', [layoutInfo.x, layoutInfo.y]); // generate a bezire Curve for each edge
graph.eachEdge(function (edge) {
var curve = new SankeyShape();
curve.dataIndex = edge.dataIndex;
curve.seriesIndex = seriesModel.seriesIndex;
curve.dataType = 'edge';
var lineStyleModel = edge.getModel('lineStyle');
var curvature = lineStyleModel.get('curveness');
var n1Layout = edge.node1.getLayout();
var node1Model = edge.node1.getModel();
var dragX1 = node1Model.get('localX');
var dragY1 = node1Model.get('localY');
var n2Layout = edge.node2.getLayout();
var node2Model = edge.node2.getModel();
var dragX2 = node2Model.get('localX');
var dragY2 = node2Model.get('localY');
var edgeLayout = edge.getLayout();
var x1;
var y1;
var x2;
var y2;
var cpx1;
var cpy1;
var cpx2;
var cpy2;
curve.shape.extent = Math.max(1, edgeLayout.dy);
curve.shape.orient = orient;
if (orient === 'vertical') {
x1 = (dragX1 != null ? dragX1 * width : n1Layout.x) + edgeLayout.sy;
y1 = (dragY1 != null ? dragY1 * height : n1Layout.y) + n1Layout.dy;
x2 = (dragX2 != null ? dragX2 * width : n2Layout.x) + edgeLayout.ty;
y2 = dragY2 != null ? dragY2 * height : n2Layout.y;
cpx1 = x1;
cpy1 = y1 * (1 - curvature) + y2 * curvature;
cpx2 = x2;
cpy2 = y1 * curvature + y2 * (1 - curvature);
} else {
x1 = (dragX1 != null ? dragX1 * width : n1Layout.x) + n1Layout.dx;
y1 = (dragY1 != null ? dragY1 * height : n1Layout.y) + edgeLayout.sy;
x2 = dragX2 != null ? dragX2 * width : n2Layout.x;
y2 = (dragY2 != null ? dragY2 * height : n2Layout.y) + edgeLayout.ty;
cpx1 = x1 * (1 - curvature) + x2 * curvature;
cpy1 = y1;
cpx2 = x1 * curvature + x2 * (1 - curvature);
cpy2 = y2;
}
curve.setShape({
x1: x1,
y1: y1,
x2: x2,
y2: y2,
cpx1: cpx1,
cpy1: cpy1,
cpx2: cpx2,
cpy2: cpy2
});
curve.setStyle(lineStyleModel.getItemStyle()); // Special color, use source node color or target node color
switch (curve.style.fill) {
case 'source':
curve.style.fill = edge.node1.getVisual('color');
break;
case 'target':
curve.style.fill = edge.node2.getVisual('color');
break;
}
graphic.setHoverStyle(curve, edge.getModel('emphasis.lineStyle').getItemStyle());
group.add(curve);
edgeData.setItemGraphicEl(edge.dataIndex, curve);
}); // Generate a rect for each node
graph.eachNode(function (node) {
var layout = node.getLayout();
var itemModel = node.getModel();
var dragX = itemModel.get('localX');
var dragY = itemModel.get('localY');
var labelModel = itemModel.getModel('label');
var labelHoverModel = itemModel.getModel('emphasis.label');
var rect = new graphic.Rect({
shape: {
x: dragX != null ? dragX * width : layout.x,
y: dragY != null ? dragY * height : layout.y,
width: layout.dx,
height: layout.dy
},
style: itemModel.getModel('itemStyle').getItemStyle()
});
var hoverStyle = node.getModel('emphasis.itemStyle').getItemStyle();
graphic.setLabelStyle(rect.style, hoverStyle, labelModel, labelHoverModel, {
labelFetcher: seriesModel,
labelDataIndex: node.dataIndex,
defaultText: node.id,
isRectText: true
});
rect.setStyle('fill', node.getVisual('color'));
graphic.setHoverStyle(rect, hoverStyle);
group.add(rect);
nodeData.setItemGraphicEl(node.dataIndex, rect);
rect.dataType = 'node';
});
nodeData.eachItemGraphicEl(function (el, dataIndex) {
var itemModel = nodeData.getItemModel(dataIndex);
if (itemModel.get('draggable')) {
el.drift = function (dx, dy) {
sankeyView._focusAdjacencyDisabled = true;
this.shape.x += dx;
this.shape.y += dy;
this.dirty();
api.dispatchAction({
type: 'dragNode',
seriesId: seriesModel.id,
dataIndex: nodeData.getRawIndex(dataIndex),
localX: this.shape.x / width,
localY: this.shape.y / height
});
};
el.ondragend = function () {
sankeyView._focusAdjacencyDisabled = false;
};
el.draggable = true;
el.cursor = 'move';
}
if (itemModel.get('focusNodeAdjacency')) {
el.off('mouseover').on('mouseover', function () {
if (!sankeyView._focusAdjacencyDisabled) {
api.dispatchAction({
type: 'focusNodeAdjacency',
seriesId: seriesModel.id,
dataIndex: el.dataIndex
});
}
});
el.off('mouseout').on('mouseout', function () {
if (!sankeyView._focusAdjacencyDisabled) {
api.dispatchAction({
type: 'unfocusNodeAdjacency',
seriesId: seriesModel.id
});
}
});
}
});
edgeData.eachItemGraphicEl(function (el, dataIndex) {
var edgeModel = edgeData.getItemModel(dataIndex);
if (edgeModel.get('focusNodeAdjacency')) {
el.off('mouseover').on('mouseover', function () {
if (!sankeyView._focusAdjacencyDisabled) {
api.dispatchAction({
type: 'focusNodeAdjacency',
seriesId: seriesModel.id,
edgeDataIndex: el.dataIndex
});
}
});
el.off('mouseout').on('mouseout', function () {
if (!sankeyView._focusAdjacencyDisabled) {
api.dispatchAction({
type: 'unfocusNodeAdjacency',
seriesId: seriesModel.id
});
}
});
}
});
if (!this._data && seriesModel.get('animation')) {
group.setClipPath(createGridClipShape(group.getBoundingRect(), seriesModel, function () {
group.removeClipPath();
}));
}
this._data = seriesModel.getData();
},
dispose: function () {},
focusNodeAdjacency: function (seriesModel, ecModel, api, payload) {
var data = this._model.getData();
var graph = data.graph;
var dataIndex = payload.dataIndex;
var itemModel = data.getItemModel(dataIndex);
var edgeDataIndex = payload.edgeDataIndex;
if (dataIndex == null && edgeDataIndex == null) {
return;
}
var node = graph.getNodeByIndex(dataIndex);
var edge = graph.getEdgeByIndex(edgeDataIndex);
graph.eachNode(function (node) {
fadeOutItem(node, nodeOpacityPath, 0.1);
});
graph.eachEdge(function (edge) {
fadeOutItem(edge, lineOpacityPath, 0.1);
});
if (node) {
fadeInItem(node, nodeOpacityPath);
var focusNodeAdj = itemModel.get('focusNodeAdjacency');
if (focusNodeAdj === 'outEdges') {
zrUtil.each(node.outEdges, function (edge) {
if (edge.dataIndex < 0) {
return;
}
fadeInItem(edge, lineOpacityPath);
fadeInItem(edge.node2, nodeOpacityPath);
});
} else if (focusNodeAdj === 'inEdges') {
zrUtil.each(node.inEdges, function (edge) {
if (edge.dataIndex < 0) {
return;
}
fadeInItem(edge, lineOpacityPath);
fadeInItem(edge.node1, nodeOpacityPath);
});
} else if (focusNodeAdj === 'allEdges') {
zrUtil.each(node.edges, function (edge) {
if (edge.dataIndex < 0) {
return;
}
fadeInItem(edge, lineOpacityPath);
fadeInItem(edge.node1, nodeOpacityPath);
fadeInItem(edge.node2, nodeOpacityPath);
});
}
}
if (edge) {
fadeInItem(edge, lineOpacityPath);
fadeInItem(edge.node1, nodeOpacityPath);
fadeInItem(edge.node2, nodeOpacityPath);
}
},
unfocusNodeAdjacency: function (seriesModel, ecModel, api, payload) {
var graph = this._model.getGraph();
graph.eachNode(function (node) {
fadeOutItem(node, nodeOpacityPath);
});
graph.eachEdge(function (edge) {
fadeOutItem(edge, lineOpacityPath);
});
}
}); // Add animation to the view
function createGridClipShape(rect, seriesModel, cb) {
var rectEl = new graphic.Rect({
shape: {
x: rect.x - 10,
y: rect.y - 10,
width: 0,
height: rect.height + 20
}
});
graphic.initProps(rectEl, {
shape: {
width: rect.width + 20,
height: rect.height + 20
}
}, seriesModel, cb);
return rectEl;
}