453 lines
14 KiB
JavaScript
453 lines
14 KiB
JavaScript
|
|
/*
|
|
* 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.
|
|
*/
|
|
|
|
var graphic = require("../../util/graphic");
|
|
|
|
var echarts = require("../../echarts");
|
|
|
|
var zrUtil = require("zrender/lib/core/util");
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
var nodeOpacityPath = ['itemStyle', 'opacity'];
|
|
var hoverNodeOpacityPath = ['emphasis', 'itemStyle', 'opacity'];
|
|
var lineOpacityPath = ['lineStyle', 'opacity'];
|
|
var hoverLineOpacityPath = ['emphasis', '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.traverse(function (child) {
|
|
if (child.type !== 'group') {
|
|
child.setStyle('opacity', opacity);
|
|
}
|
|
}); // Support emphasis here.
|
|
|
|
el.highlight && el.highlight();
|
|
}
|
|
|
|
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();
|
|
},
|
|
highlight: function () {
|
|
this.trigger('emphasis');
|
|
},
|
|
downplay: function () {
|
|
this.trigger('normal');
|
|
}
|
|
});
|
|
|
|
var _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';
|
|
}
|
|
|
|
el.highlight = function () {
|
|
this.trigger('emphasis');
|
|
};
|
|
|
|
el.downplay = function () {
|
|
this.trigger('normal');
|
|
};
|
|
|
|
el.focusNodeAdjHandler && el.off('mouseover', el.focusNodeAdjHandler);
|
|
el.unfocusNodeAdjHandler && el.off('mouseout', el.unfocusNodeAdjHandler);
|
|
|
|
if (itemModel.get('focusNodeAdjacency')) {
|
|
el.on('mouseover', el.focusNodeAdjHandler = function () {
|
|
if (!sankeyView._focusAdjacencyDisabled) {
|
|
sankeyView._clearTimer();
|
|
|
|
api.dispatchAction({
|
|
type: 'focusNodeAdjacency',
|
|
seriesId: seriesModel.id,
|
|
dataIndex: el.dataIndex
|
|
});
|
|
}
|
|
});
|
|
el.on('mouseout', el.unfocusNodeAdjHandler = function () {
|
|
if (!sankeyView._focusAdjacencyDisabled) {
|
|
sankeyView._dispatchUnfocus(api);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
edgeData.eachItemGraphicEl(function (el, dataIndex) {
|
|
var edgeModel = edgeData.getItemModel(dataIndex);
|
|
el.focusNodeAdjHandler && el.off('mouseover', el.focusNodeAdjHandler);
|
|
el.unfocusNodeAdjHandler && el.off('mouseout', el.unfocusNodeAdjHandler);
|
|
|
|
if (edgeModel.get('focusNodeAdjacency')) {
|
|
el.on('mouseover', el.focusNodeAdjHandler = function () {
|
|
if (!sankeyView._focusAdjacencyDisabled) {
|
|
sankeyView._clearTimer();
|
|
|
|
api.dispatchAction({
|
|
type: 'focusNodeAdjacency',
|
|
seriesId: seriesModel.id,
|
|
edgeDataIndex: el.dataIndex
|
|
});
|
|
}
|
|
});
|
|
el.on('mouseout', el.unfocusNodeAdjHandler = function () {
|
|
if (!sankeyView._focusAdjacencyDisabled) {
|
|
sankeyView._dispatchUnfocus(api);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
if (!this._data && seriesModel.get('animation')) {
|
|
group.setClipPath(createGridClipShape(group.getBoundingRect(), seriesModel, function () {
|
|
group.removeClipPath();
|
|
}));
|
|
}
|
|
|
|
this._data = seriesModel.getData();
|
|
},
|
|
dispose: function () {
|
|
this._clearTimer();
|
|
},
|
|
_dispatchUnfocus: function (api) {
|
|
var self = this;
|
|
|
|
this._clearTimer();
|
|
|
|
this._unfocusDelayTimer = setTimeout(function () {
|
|
self._unfocusDelayTimer = null;
|
|
api.dispatchAction({
|
|
type: 'unfocusNodeAdjacency',
|
|
seriesId: self._model.id
|
|
});
|
|
}, 500);
|
|
},
|
|
_clearTimer: function () {
|
|
if (this._unfocusDelayTimer) {
|
|
clearTimeout(this._unfocusDelayTimer);
|
|
this._unfocusDelayTimer = null;
|
|
}
|
|
},
|
|
focusNodeAdjacency: function (seriesModel, ecModel, api, payload) {
|
|
var data = seriesModel.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, hoverNodeOpacityPath);
|
|
var focusNodeAdj = itemModel.get('focusNodeAdjacency');
|
|
|
|
if (focusNodeAdj === 'outEdges') {
|
|
zrUtil.each(node.outEdges, function (edge) {
|
|
if (edge.dataIndex < 0) {
|
|
return;
|
|
}
|
|
|
|
fadeInItem(edge, hoverLineOpacityPath);
|
|
fadeInItem(edge.node2, hoverNodeOpacityPath);
|
|
});
|
|
} else if (focusNodeAdj === 'inEdges') {
|
|
zrUtil.each(node.inEdges, function (edge) {
|
|
if (edge.dataIndex < 0) {
|
|
return;
|
|
}
|
|
|
|
fadeInItem(edge, hoverLineOpacityPath);
|
|
fadeInItem(edge.node1, hoverNodeOpacityPath);
|
|
});
|
|
} else if (focusNodeAdj === 'allEdges') {
|
|
zrUtil.each(node.edges, function (edge) {
|
|
if (edge.dataIndex < 0) {
|
|
return;
|
|
}
|
|
|
|
fadeInItem(edge, hoverLineOpacityPath);
|
|
edge.node1 !== node && fadeInItem(edge.node1, hoverNodeOpacityPath);
|
|
edge.node2 !== node && fadeInItem(edge.node2, hoverNodeOpacityPath);
|
|
});
|
|
}
|
|
}
|
|
|
|
if (edge) {
|
|
fadeInItem(edge, hoverLineOpacityPath);
|
|
fadeInItem(edge.node1, hoverNodeOpacityPath);
|
|
fadeInItem(edge.node2, hoverNodeOpacityPath);
|
|
}
|
|
},
|
|
unfocusNodeAdjacency: function (seriesModel, ecModel, api, payload) {
|
|
var graph = seriesModel.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
|
|
}
|
|
}, seriesModel, cb);
|
|
return rectEl;
|
|
}
|
|
|
|
module.exports = _default; |