refactor(util): refactor radial layout method & add demo

This commit is contained in:
yilin.qyl 2019-02-12 18:04:11 +08:00
parent cad47b4b25
commit 004cb3ce56
5 changed files with 1413 additions and 26 deletions

1224
demos/assets/hierarchy.js Normal file

File diff suppressed because it is too large Load Diff

151
demos/tree-graph.html Normal file
View File

@ -0,0 +1,151 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../test/unit/graph/tree-graph-spec.js"></script>
</head>
<style>
</style>
<body>
<select id="layout">
<option value="dendrogram">基础树</option>
<option value="compactBox">紧凑树</option>
</select>
<input type="checkbox" id="radial" /><label for="radial">环形布局</label>
<div id="mountNode"></div>
<script src="assets/hierarchy.js"></script>
<script src="../build/g6.js"></script>
<script>
let currentLayout = 'dendrogram';
let radial = false;
const layouts = {
dendrogram: function(data) {
return Hierarchy.dendrogram(data, {
direction: 'LR', // H / V / LR / RL / TB / BT
nodeSep: 50,
rankSep: 100
})},
compactBox: function(data) {
return Hierarchy.compactBox(data, { direction: 'LR' });
}
};
const graph = new G6.TreeGraph({
container: 'mountNode',
width: 500,
height: 500,
pixelRatio: 2,
modes: {
default: [{
type: 'collapse-expand',
onChange(item, collapsed) {
const data = item.get('model').data;
data.collapsed = collapsed;
return true;
}
}, 'drag-canvas']
},
layout: layouts.dendrogram
});
const data = {
isRoot: true,
id: 'Root',
children: [
{
id: 'SubTreeNode1',
children: [
{
id: 'SubTreeNode1.1'
},
{
id: 'SubTreeNode1.2'
}
]
},
{
id: 'SubTreeNode2',
children: [
{
id: 'SubTreeNode2.1'
},
{
id: 'SubTreeNode2.2',
children: [
{
id: 'SubTreeNode1.2.1'
},
{
id: 'SubTreeNode1.2.2'
},
{
id: 'SubTreeNode1.2.3'
}
]
}
]
}, {
id: 'SubTreeNode3'
}, {
id: 'SubTreeNode4'
}, {
id: 'SubTreeNode5'
}, {
id: 'SubTreeNode6'
}, {
id: 'SubTreeNode7',
children: [
{
id: 'SubTreeNode3.1'
},
{
id: 'SubTreeNode3.2'
},
{
id: 'SubTreeNode3.3'
}
]
}, {
id: 'SubTreeNode8'
}, {
id: 'SubTreeNode9'
}, {
id: 'SubTreeNode10'
}, {
id: 'SubTreeNode11'
}
]
};
graph.data(data);
graph.render();
graph.fitView();
document.getElementById('radial').addEventListener('change', e => {
if (e.target.checked) {
graph.set('layout', (data) => {
data = layouts[currentLayout](data);
G6.Util.radialLayout(data);
return data;
});
graph.render();
graph.fitView();
} else {
radial = false;
graph.set('layout', layouts[currentLayout]);
graph.render();
graph.fitView();
}
});
document.getElementById('layout').addEventListener('change', (e) => {
const layout = e.target.value;
if (currentLayout !== layout) {
currentLayout = layout;
graph.set('layout', layouts[currentLayout]);
graph.render();
if (radial) {
G6.Util.radialLayout(graph, 'LR');
}
graph.fitView();
}
});
</script>
</body>
</html>

View File

@ -124,7 +124,7 @@ module.exports = {
x: data.x, x: data.x,
y: data.y y: data.y
}; };
if (!point.x || !point.y) { if (Util.isNil(point.x) || Util.isNil(point.y)) {
const model = node.get('parent').get('model'); const model = node.get('parent').get('model');
point.x = model.x; point.x = model.x;
point.y = model.y; point.y = model.y;

View File

@ -4,6 +4,16 @@
*/ */
const MathUtil = require('./math'); const MathUtil = require('./math');
const BaseUtil = require('./base');
function traverse(data, fn) {
if (fn(data) === false) {
return;
}
BaseUtil.each(data.children, child => {
traverse(child, fn);
});
}
const GraphicUtil = { const GraphicUtil = {
getBBox(element, parent) { getBBox(element, parent) {
@ -30,12 +40,15 @@ const GraphicUtil = {
maxY: rightBottom.y maxY: rightBottom.y
}; };
}, },
radialLayout(graph, layout) { traverseTree(data, fn) {
if (typeof fn !== 'function') {
return;
}
traverse(data, fn);
},
radialLayout(data, layout) {
// 布局方式有 H / V / LR / RL / TB / BT // 布局方式有 H / V / LR / RL / TB / BT
const VERTICAL_LAYOUTS = [ 'V', 'TB', 'BT' ]; const VERTICAL_LAYOUTS = [ 'V', 'TB', 'BT' ];
const width = graph.get('width');
const height = graph.get('height');
const radius = Math.min(width, height);
const min = { const min = {
x: Infinity, x: Infinity,
y: Infinity y: Infinity
@ -52,32 +65,31 @@ const GraphicUtil = {
radScale = 'x'; radScale = 'x';
rScale = 'y'; rScale = 'y';
} }
graph.get('nodes').forEach(node => { let count = 0;
const model = node.get('model'); this.traverseTree(data, node => {
if (model.x > max.x) { count++;
max.x = model.x; if (node.x > max.x) {
max.x = node.x;
} }
if (model.x < min.x) { if (node.x < min.x) {
min.x = model.x; min.x = node.x;
} }
if (model.y > max.y) { if (node.y > max.y) {
max.y = model.y; max.y = node.y;
} }
if (model.y < min.y) { if (node.y < min.y) {
min.y = model.y; min.y = node.y;
} }
}); });
const avgRad = Math.PI * 2 / graph.get('nodes').length; const avgRad = Math.PI * 2 / count;
const radDiff = max[radScale] - min[radScale]; const radDiff = max[radScale] - min[radScale];
graph.get('nodes').forEach(node => { this.traverseTree(data, node => {
const model = node.get('model'); const radial = (node[radScale] - min[radScale]) / radDiff * (Math.PI * 2 - avgRad) + avgRad;
const radial = (model[radScale] - min[radScale]) / radDiff * (Math.PI * 2 - avgRad) + avgRad; const r = node[rScale];
const r = model[rScale] / max[rScale] * radius; node.x = r * Math.cos(radial);
model.x = r * Math.cos(radial); node.y = r * Math.sin(radial);
model.y = r * Math.sin(radial);
}); });
graph.refreshPositions(); return data;
graph.fitView();
} }
}; };

View File

@ -441,7 +441,7 @@ describe('tree graph with layout', () => {
graph.emit('node:click', { item: parent }); graph.emit('node:click', { item: parent });
}, 600); }, 600);
}); });
const treeData = { /* const treeData = {
isRoot: true, isRoot: true,
id: 'Root', id: 'Root',
children: [ children: [
@ -535,5 +535,5 @@ describe('tree graph with layout', () => {
graph.data(treeData); graph.data(treeData);
graph.render(); graph.render();
G6.Util.radialLayout(graph, 'TB'); G6.Util.radialLayout(graph, 'TB');
}); });*/
}); });