feat: add sankye table interface && demo

This commit is contained in:
huangtong.ht 2018-11-12 10:30:34 +08:00
parent 78bacb9bbd
commit 7228bcd802
6 changed files with 355 additions and 26 deletions

View File

@ -0,0 +1,167 @@
[
{
"group": "Tencent",
"company": "Didi Chuxing",
"cap": 56
},
{
"group": "Alibaba",
"company": "Didi Chuxing",
"cap": 56
},
{
"group": "Ant",
"company": "Didi Chuxing",
"cap": 56
},
{
"group": "Tencent",
"company": "Meituan-Dianping",
"cap": 30
},
{
"group": "Alibaba",
"company": "Meituan-Dianping",
"cap": 30
},
{
"group": "Tencent",
"company": "Pingduoduo",
"cap": 21
},
{
"group": "Alibaba",
"company": "Ele.me",
"cap": 9.5
},
{
"group": "Tencent",
"company": "China Literature",
"cap": 7.6
},
{
"group": "Alibaba",
"company": "Zhong An Insurance",
"cap": 6.5
},
{
"group": "Tencent",
"company": "Guahao Tech/Weiyi/WeDoctor",
"cap": 6
},
{
"group": "Alibaba",
"company": "Easyhome",
"cap": 5.7
},
{
"group": "Alibaba",
"company": "Meizu",
"cap": 4.6
},
{
"group": "Alibaba",
"company": "SenseTime",
"cap": 4.5
},
{
"group": "Alibaba",
"company": "Xiaohongshu/Little Red Book",
"cap": 3
},
{
"group": "Tencent",
"company": "Xiaohongshu/Little Red Book",
"cap": 3
},
{
"group": "Tencent",
"company": "Kuaishou",
"cap": 3
},
{
"group": "Tencent",
"company": "VIPKid",
"cap": 3
},
{
"group": "Tencent",
"company": "NIO",
"cap": 2.9
},
{
"group": "Alibaba",
"company": "Cambricon",
"cap": 2
},
{
"group": "Tencent",
"company": "Weiying Technology/Wepiao",
"cap": 2
},
{
"group": "Tencent",
"company": "Full Truck Alliance Group",
"cap": 2
},
{
"group": "Ant",
"company": "Ucommune",
"cap": 1.7
},
{
"group": "Tencent",
"company": "Douyu",
"cap": 1.5
},
{
"group": "Ant",
"company": "Hellobike",
"cap": 1.5
},
{
"group": "Ant",
"company": "UrWork",
"cap": 1.4
},
{
"group": "Ant",
"company": "ofo",
"cap": 1
},
{
"group": "Alibaba",
"company": "ofo",
"cap": 1
},
{
"group": "Alibaba",
"company": "iTutorGroup",
"cap": 1
},
{
"group": "Alibaba",
"company": "Face++",
"cap": 1
},
{
"group": "Ant",
"company": "Face++",
"cap": 1
},
{
"group": "Tencent",
"company": "iCarbonX",
"cap": 1
},
{
"group": "Tencent",
"company": "Zhihu",
"cap": 1
},
{
"group": "Tencent",
"company": "Ding Xiang Yuan",
"cap": 1
}
]

View File

@ -0,0 +1,126 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>画廊-美国总统信息桑基图</title>
</head>
<body>
<div id="mountNode"></div>
<script src="./assets/jquery-3.2.1.min.js"></script>
<script src="../build/g6.js"></script>
<script src="../build/plugin.template.tableSankey.js"></script>
<script>
$.getJSON('./assets/data/atm-investment.json', table => {
const sankeyPlugin = new G6.Plugins['template.tableSankey']({
table,
onBeforeSankeyProcessorExecute(sankeyProcessor) {
sankeyProcessor.nodeWidth(4);
sankeyProcessor.nodePadding(16);
},
onBeforeRender(graph) {
const width = graph.getWidth();
graph.node({
color() {
return 'black';
},
style(model) {
const style = {
lineWidth:0
}
if (model.field === 'cap') {
style.fillOpacity = 0;
}
return style;
},
label: model => {
const label = {
text: model.field === 'cap' ? '$ ' + model.fieldValue : model.fieldValue,
...this.labelStyle
};
if (model.x > width / 2) {
label.textAlign = 'right';
} else {
label.textAlign = 'left';
}
return label;
},
labelOffsetX(model) {
if (model.field === 'cap') {
return 0;
}
const labelGap = 8;
if (model.x > width / 2) {
return -(model.x1 - model.x0) / 2 - labelGap;
}
return (model.x1 - model.x0) / 2 + labelGap;
}
});
graph.edge({
sourceOffset(model) {
const {source, target} = model;
if(target.indexOf('cap') !== -1) {
const sourceItem = graph.find(source);
const sourceLabel = sourceItem.getLabel();
const sourceLabelBox = sourceLabel.getBBox();
return sourceLabelBox.width+10;
}
},
targetOffset(model) {
const {target} = model;
if(target.indexOf('cap') !== -1) {
const targetItem = graph.find(target);
const targetLabel = targetItem.getLabel();
const targetLabelBox = targetLabel.getBBox();
return targetLabelBox.width;
}
},
style(model){
const {source, target} = model;
const strokeOpacity = 0.6;
let stroke = '#333';
if (source === 'groupTencent') {
stroke = "#61C489";
} else if(source === 'groupAlibaba') {
stroke = '#E7AC45';
} else if(source === 'groupAnt') {
stroke = '#326DF6';
}
if(target.indexOf('cap') !== -1) {
return {
lineWidth: 1,
lineDash: [1, 1]
};
}
return {
stroke,
strokeOpacity
};
}
});
},
combine({ field, value, row }) {
if (field === 'cap') {
return row.company + field + value;
}
return field + value;
}
});
new G6.Graph({
container: 'mountNode',
width: 500,
height: 600,
fitView: 'cc',
animate: true,
plugins: [ sankeyPlugin ]
});
});
</script>
</body>
</html>

View File

@ -98,7 +98,7 @@
"screenshot": "node ./bin/screenshot.js", "screenshot": "node ./bin/screenshot.js",
"start": "npm run dev", "start": "npm run dev",
"test": "torch --compile --renderer --recursive ./test/unit", "test": "torch --compile --renderer --recursive ./test/unit",
"test-live": "torch --compile --interactive --watch --recursive ./test/unit/", "test-live": "torch --compile --interactive --watch --recursive ./test/unit/plugins/template.tableSankey-spec",
"watch": "webpack --config webpack-dev.config.js", "watch": "webpack --config webpack-dev.config.js",
"win-dev": "node ./bin/win-dev.js" "win-dev": "node ./bin/win-dev.js"
}, },

View File

@ -42,20 +42,20 @@ G6.registerEdge('sankey-edge', {
const targetBox = target.getBBox(); const targetBox = target.getBBox();
const sourceModel = source.getModel(); const sourceModel = source.getModel();
const targetModel = target.getModel(); const targetModel = target.getModel();
let { y0, y1 } = model; let { y0, y1, sourceOffset = 0, targetOffset = 0 } = model;
y0 = sourceBox.minY + y0 - sourceModel.y0; y0 = sourceBox.minY + y0 - sourceModel.y0;
y1 = targetBox.minY + y1 - targetModel.y0; y1 = targetBox.minY + y1 - targetModel.y0;
if (sourceBox.centerX < targetBox.centerX) { let startX = sourceBox.maxX + sourceOffset;
const hgap = targetBox.minX - sourceBox.maxX; let endX = targetBox.minX - targetOffset;
return [ let hgap = endX - startX;
[ 'M', sourceBox.maxX, y0 ], if (sourceBox.centerX > targetBox.centerX) {
[ 'C', sourceBox.maxX + hgap / 4, y0, targetBox.minX - hgap / 2, y1, targetBox.minX, y1 ] startX = targetBox.maxX + targetOffset;
]; endX = sourceBox.minX - sourceOffset;
hgap = endX - startX;
} }
const hgap = sourceBox.minX - targetBox.maxX;
return [ return [
[ 'M', targetBox.maxX, y1 ], [ 'M', startX, y0 ],
[ 'C', targetBox.maxX + hgap / 4, y1, sourceBox.minX - hgap / 2, y0, sourceBox.minX, y0 ] [ 'C', startX + hgap / 4, y0, endX - hgap / 2, y1, endX, y1 ]
]; ];
} }
}); });
@ -67,27 +67,30 @@ G6.registerGuide('col-names', {
const nodes = graph.getNodes(); const nodes = graph.getNodes();
const model = item.getModel(); const model = item.getModel();
const colMap = {}; const colMap = {};
const { textStyle } = model; const { textStyle, fields } = model;
let minY = Infinity; let minY = Infinity;
nodes.forEach(node => { nodes.forEach(node => {
const model = node.getModel(); const model = node.getModel();
const { field, y, x } = model; const { field, y, x, colIndex } = model;
if (minY > y) { if (minY > y) {
minY = y; minY = y;
} }
if (!colMap[field]) { if (!colMap[field]) {
colMap[field] = { colMap[field] = {
field, field,
x x,
colIndex
}; };
} }
}); });
Util.each(colMap, ({ field, x }) => { Util.each(colMap, ({ field, x, colIndex }) => {
group.addShape('text', { group.addShape('text', {
attrs: { attrs: {
text: field, text: field,
x, x,
y: minY - 12, y: minY - 12,
textAlign: colIndex === fields.length - 1 ? 'right' : 'left',
...textStyle ...textStyle
} }
}); });
@ -123,8 +126,7 @@ class Plugin {
* @type {object} colNameTextStyle - col name text style * @type {object} colNameTextStyle - col name text style
*/ */
colNameTextStyle: { colNameTextStyle: {
fill: '#333', fill: '#333'
textAlign: 'center'
}, },
/** /**
@ -147,15 +149,25 @@ class Plugin {
/** /**
* @type {function} combine - comine the node id * @type {function} combine - comine the node id
* @param {object} cfg - combine cfg * @param {object} cfg - combine cfg
* @property {string} cfg.field - input object * @property {string} cfg.field -field
* @property {string} cfg.value - input object * @property {string} cfg.value - value
* @property {string} cfg.colIndex - input object * @property {string} cfg.colIndex - colIndex
* @property {object} cfg.rowIndex - input object * @property {object} cfg.rowIndex - rowIndex
* @property {object} cfg.row - row
* @return {string} combine id * @return {string} combine id
*/ */
combine({ field, value }) { combine({ field, value }) {
return field + value; return field + value;
} },
/**
* @type {function} onBeforeSankeyProcessorExecute - trigger before sankeyProcessor execute
*/
onBeforeSankeyProcessorExecute(/* sankeyProcessor */) {},
/**
* @type {function} onBeforeSankeyProcessorExecute - trigger after sankeyProcessor execute
*/
onAfterSankeyProcessorExecute(/* sankeyProcessor */) {}
}, options); }, options);
} }
_getFields() { _getFields() {
@ -181,7 +193,7 @@ class Plugin {
table.forEach((row, rowIndex) => { table.forEach((row, rowIndex) => {
fields.forEach((field, colIndex) => { fields.forEach((field, colIndex) => {
const value = row[field]; const value = row[field];
const id = this.combine({ field, value, colIndex, rowIndex }); const id = this.combine({ field, value, colIndex, rowIndex, row });
if (!map[id]) { if (!map[id]) {
map[id] = { map[id] = {
id, id,
@ -206,8 +218,8 @@ class Plugin {
} }
const value = row[field]; const value = row[field];
const nextValue = row[nextField]; const nextValue = row[nextField];
const source = this.combine({ field, value, colIndex, rowIndex }); const source = this.combine({ field, value, colIndex, rowIndex, row });
const target = this.combine({ field: nextField, value: nextValue, colIndex: nextColIndex, rowIndex }); const target = this.combine({ field: nextField, value: nextValue, colIndex: nextColIndex, rowIndex, row });
const id = source + '-' + target; const id = source + '-' + target;
if (!map[id]) { if (!map[id]) {
map[id] = { map[id] = {
@ -276,7 +288,8 @@ class Plugin {
if (this.showColName) { if (this.showColName) {
guides.push({ guides.push({
shape: 'col-names', shape: 'col-names',
textStyle: this.colNameTextStyle textStyle: this.colNameTextStyle,
fields: this._getFields()
}); });
} }
return guides; return guides;
@ -290,7 +303,9 @@ class Plugin {
edges: this._getEdges(table, fields), edges: this._getEdges(table, fields),
guides: this._getGuides() guides: this._getGuides()
}; };
this.onBeforeSankeyProcessorExecute(sankeyProcessor);
sankeyProcessor(data); sankeyProcessor(data);
this.onAfterSankeyProcessorExecute(sankeyProcessor);
data.nodes.forEach(node => { data.nodes.forEach(node => {
node.x = (node.x0 + node.x1) / 2; node.x = (node.x0 + node.x1) / 2;
node.y = (node.y0 + node.y1) / 2; node.y = (node.y0 + node.y1) / 2;

View File

View File

@ -0,0 +1,21 @@
const G6 = require('../../../src/index');
const container = document.createElement('div');
const table = require('../../../demos/assets/data/atm-investment.json');
container.setAttribute('data-test-spec', 'plugin/template.tableSankey-spec.js');
document.body.appendChild(container);
require('../../../plugins/template.tableSankey/');
describe('tableSankey test', () => {
const sankeyPlugin = new G6.Plugins['template.tableSankey']({
table
});
const graph = new G6.Graph({
container,
height: 600,
fitView: 'cc',
animate: true,
plugins: [ sankeyPlugin ]
});
it('destroy', () => {
graph.destroy();
});
});