test: tool.mapper-spec

This commit is contained in:
Yanyan-Wang 2018-08-07 13:29:43 +08:00
commit 0bea2f6f29
32 changed files with 715 additions and 908 deletions

View File

@ -58,7 +58,7 @@
graph = new G6.Graph({ graph = new G6.Graph({
id: 'mountNode', // dom id id: 'mountNode', // dom id
fitView: 'autoZoom', fitView: 'autoZoom',
plugins: [new Plugin({max_iteration: 1000, kg: 2, kr: 40, prev_overlapping: true}), plugins: [new Plugin({maxIteration: 1000, kg: 2, kr: 40, prevOverlapping: true}),
new FisheyePlugin({radius: 200}), new FisheyePlugin({radius: 200}),
nodeSizeMapper, nodeColorMapper nodeSizeMapper, nodeColorMapper
], ],

View File

@ -25,11 +25,11 @@
const Mapper = G6.Plugins['tool.mapper']; const Mapper = G6.Plugins['tool.mapper'];
const MaxSpanningForestPlugin = G6.Plugins['template.maxSpanningForest']; const MaxSpanningForestPlugin = G6.Plugins['template.maxSpanningForest'];
//the instances of plugins //the instances of plugins
const maxSpanningForest = new MaxSpanningForestPlugin({ let maxSpanningForest = new MaxSpanningForestPlugin({
layoutCfg: { layoutCfg: {
max_iteration: 600, maxIteration: 600,
kg: 10, kg: 10,
prev_overlapping: true, prevOverlapping: true,
onLayoutComplete: function () { onLayoutComplete: function () {
const minimap = document.getElementById('minimap'); const minimap = document.getElementById('minimap');
const legend = document.getElementById('legend'); const legend = document.getElementById('legend');
@ -38,24 +38,34 @@
} }
} }
}); });
const nodeSizeMapper = new Mapper('node', 'userview', 'size', [20, 50], { let nodeSizeMapper = new Mapper('node', 'userview', 'size', [20, 50], {
legendCfg: { legendCfg: {
containerId: 'legend', containerId: 'legend',
title: 'UV', title: 'UV',
layout: 'horizontal' layout: 'horizontal',
// formatter: function(num) {
// console.log(num);
// num = num * num;
// if (num >= 100000000) {
// return num / 100000000 + '亿';
// } if (num >= 10000) {
// return num / 10000 + '万';
// }
// return num;
// }
} }
}); });
const edgeSizeMapper = new Mapper('edge', 'userview', 'size', [1, 16], { let edgeSizeMapper = new Mapper('edge', 'userview', 'size', [1, 16], {
legendCfg: null legendCfg: null
}); });
const nodeColorMapper = new Mapper('node', 'stayTime', 'color', ['#BAE7FF', '#1890FF', '#0050B3'], { let nodeColorMapper = new Mapper('node', 'stayTime', 'color', ['#BAE7FF', '#1890FF', '#0050B3'], {
legendCfg: { legendCfg: {
containerId: 'legend', containerId: 'legend',
title: 'Stay Time', title: 'Stay Time',
layout: 'horizontal' layout: 'horizontal'
} }
}); });
const minimapPlugin = new G6.Plugins['tool.minimap']({ let minimapPlugin = new G6.Plugins['tool.minimap']({
container: 'minimap', container: 'minimap',
width: 180, width: 180,
height: 120 height: 120
@ -67,14 +77,14 @@
id: 'mountNode', // dom id id: 'mountNode', // dom id
fitView: 'autoZoom', fitView: 'autoZoom',
plugins: [ plugins: [
maxSpanningForest, nodeSizeMapper, nodeColorMapper, edgeSizeMapper, minimapPlugin maxSpanningForest, nodeColorMapper, minimapPlugin, edgeSizeMapper, nodeSizeMapper,
], ],
modes: { modes: {
default: ['panCanvas', 'wheelZoom'] default: ['panCanvas', 'wheelZoom']
}, },
height: 500, height: 500,
}); });
graph.read(data); graph.read(Util.cloneDeep(data));
const minimap = document.getElementById('minimap'); const minimap = document.getElementById('minimap');
const legend = document.getElementById('legend'); const legend = document.getElementById('legend');
if (minimap !== undefined) minimap.style.display = 'none'; if (minimap !== undefined) minimap.style.display = 'none';

View File

@ -32,13 +32,13 @@
nodes.forEach((node, index) => { nodes.forEach((node, index) => {
if (parseInt(index / col) % 2 === 0) { if (parseInt(index / col) % 2 === 0) {
node.x = (col - index % col) * hgap; node.x = (col - index % col) * hgap;
node.ori_x = node.x; node.oriX = node.x;
} else { } else {
node.x = index % col * hgap + hgap; node.x = index % col * hgap + hgap;
node.ori_x = node.x; node.oriX = node.x;
} }
node.y = parseInt(index / col) * vgap + vgap / 2; node.y = parseInt(index / col) * vgap + vgap / 2;
node.ori_y = node.y; node.oriY = node.y;
}); });
} }
}); });

View File

@ -15,19 +15,19 @@
<script> <script>
$.getJSON('../../test/fixtures/viralMarketing.json', data => { $.getJSON('../../test/fixtures/viralMarketing.json', data => {
const Plugin = G6.Plugins['layout.forceAtlas2']; const Plugin = G6.Plugins['layout.forceAtlas2'];
const layout_params = { const layoutParams = {
max_iteration: 1500, maxIteration: 1500,
prev_overlapping: true, prevOverlapping: true,
kr: 15, kr: 15,
mode: 'normal', mode: 'normal',
barnes_hut: false, // may be counter-productive on small networks barnesHut: false, // may be counter-productive on small networks
ks: 0.1, ks: 0.1,
dissuade_hubs: false dissuadeHubs: false
}; };
graph = new G6.Graph({ graph = new G6.Graph({
id: 'mountNode', // dom id id: 'mountNode', // dom id
fitView: 'autoZoom', fitView: 'autoZoom',
plugins: [new Plugin(layout_params)], plugins: [new Plugin(layoutParams)],
minZoom: 0, minZoom: 0,
modes: { modes: {
default: ['panCanvas'] default: ['panCanvas']

View File

@ -54,11 +54,11 @@
graph.read(data); graph.read(data);
const nodes = graph.getNodes(); const nodes = graph.getNodes();
const edges = graph.getEdges(); const edges = graph.getEdges();
const re_nodes = [nodes[0], nodes[1]]; const reNodes = [nodes[0], nodes[1]];
const re_edges = [edges[0]]; const reEdges = [edges[0]];
graph.highlightSubgraph({ graph.highlightSubgraph({
re_nodes, reNodes,
re_edges reEdges
}); });
</script> </script>

View File

@ -20,6 +20,7 @@
scale: 0.5 scale: 0.5
} }
}); });
const nodeSizeMapper = new Mapper('node', 'weight', 'size', [20, 40]);
const edgeSizeMapper = new Mapper('edge', 'weight', 'size', [2, 20], { const edgeSizeMapper = new Mapper('edge', 'weight', 'size', [2, 20], {
legendCfg: null legendCfg: null
}); });
@ -28,21 +29,25 @@
id: 'node1', id: 'node1',
x: 100, x: 100,
y: 200, y: 200,
weight: 10,
class: 'class_1' class: 'class_1'
}, { }, {
id: 'node2', id: 'node2',
x: 300, x: 300,
y: 200, y: 200,
weight: 2,
class: 'class_1' class: 'class_1'
}, { }, {
id: 'node3', id: 'node3',
x: 100, x: 100,
y: 100, y: 100,
weight: 15,
class: 'class_2' class: 'class_2'
}, { }, {
id: 'node4', id: 'node4',
x: 300, x: 300,
y: 100, y: 100,
weight: 5,
class: 'class_2' class: 'class_2'
}], }],
edges: [{ edges: [{
@ -61,7 +66,7 @@
}; };
const graph = new G6.Graph({ const graph = new G6.Graph({
id: 'mountNode', // dom id id: 'mountNode', // dom id
plugins: [nodeColorMapper, edgeSizeMapper], plugins: [nodeColorMapper, edgeSizeMapper, nodeSizeMapper],
height: 1000, height: 1000,
}); });
graph.read(data); graph.read(data);

View File

@ -1,38 +0,0 @@
<!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>快速上手-网图-SVG </title>
</head>
<body>
<div id="mountNode"></div>
<script src="../build/g6.js"></script>
<script>
const data = {
nodes: [{
id: 'node1',
x: 100,
y: 200
}, {
id: 'node2',
x: 300,
y: 200
}],
edges: [{
target: 'node2',
source: 'node1'
}]
};
const graph = new G6.Graph({
container: 'mountNode',
width: 500,
height: 500
});
graph.read(data);
</script>
</body>
</html>

View File

@ -1,6 +1,6 @@
{ {
"name": "@antv/g6", "name": "@antv/g6",
"version": "2.1.0-beta", "version": "2.1.0-beta.3",
"description": "graph visualization frame work", "description": "graph visualization frame work",
"main": "build/g6.js", "main": "build/g6.js",
"homepage": "https://github.com/antvis/g6", "homepage": "https://github.com/antvis/g6",
@ -97,7 +97,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/plugins/tool.mapper-spec.js", "test-live": "torch --compile --interactive --watch --recursive ./test/unit/plugins/layout.forceAtlas2-spec.js",
"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

@ -1,128 +0,0 @@
/* BlobBuilder.js
* A complete BlobBuilder shim
* By Eli Grey
* License: MIT/X11
*/
self.BlobBuilder || self.WebKitBlobBuilder || self.MozBlobBuilder || function(view) {
let
get_class = function(object) {
return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
},
FakeBlobBuilder = view.BlobBuilder = function() {},
FakeBlob = view.Blob = function(data, type) {
this.data = data;
this.size = data.length;
this.type = type;
},
FBB_proto = FakeBlobBuilder.prototype = [],
FB_proto = FakeBlob.prototype,
FileReaderSync = view.FileReaderSync,
FileException = function(type) {
this.code = this[this.name = type];
},
file_ex_codes = (
'NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR ' +
'NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR'
).split(' '),
file_ex_code = file_ex_codes.length,
URL = view.URL = view.URL || view.webkitURL || view,
real_create_object_url,
real_revoke_object_url,
btoa = view.btoa,
ArrayBuffer = view.ArrayBuffer,
can_apply_typed_arrays = false,
can_apply_typed_arrays_test = function(pass) {
can_apply_typed_arrays = !pass;
};
while (file_ex_code--) {
FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1;
}
try {
if (ArrayBuffer) {
can_apply_typed_arrays_test.apply(0, new Uint8Array(1));
}
} catch (ex) {}
if (!URL.createObjectURL) {
URL = {};
}
real_create_object_url = URL.createObjectURL;
real_revoke_object_url = URL.revokeObjectURL;
URL.createObjectURL = function(blob) {
let type = blob.type;
if (type === null) {
type = 'application/octet-stream';
}
if (blob instanceof FakeBlob) {
if (btoa) {
return 'data:' + type + ';base64,' + btoa(blob.data);
}
return 'data:' + type + ',' + encodeURIComponent(blob.data);
} else if (real_create_object_url) {
return real_create_object_url.call(URL, blob);
}
};
URL.revokeObjectURL = function(object_url) {
if (object_url.substring(0, 5) !== 'data:' && real_revoke_object_url) {
real_revoke_object_url.call(URL, object_url);
}
};
FBB_proto.append = function(data /* , endings*/) {
const bb = this;
// decode data to a binary string
if (ArrayBuffer && data instanceof ArrayBuffer) {
if (can_apply_typed_arrays) {
bb.push(String.fromCharCode.apply(String, new Uint8Array(data)));
} else {
let
str = '',
buf = new Uint8Array(data),
i = 0,
buf_len = buf.length;
for (; i < buf_len; i++) {
str += String.fromCharCode(buf[i]);
}
}
} else if (get_class(data) === 'Blob' || get_class(data) === 'File') {
if (FileReaderSync) {
const fr = new FileReaderSync();
bb.push(fr.readAsBinaryString(data));
} else {
// async FileReader won't work as BlobBuilder is sync
throw new FileException('NOT_READABLE_ERR');
}
} else if (data instanceof FakeBlob) {
bb.push(data.data);
} else {
if (typeof data !== 'string') {
data += ''; // convert unsupported types to strings
}
// decode UTF-16 to binary string
bb.push(unescape(encodeURIComponent(data)));
}
};
FBB_proto.getBlob = function(type) {
if (!arguments.length) {
type = null;
}
return new FakeBlob(this.join(''), type);
};
FBB_proto.toString = function() {
return '[object BlobBuilder]';
};
FB_proto.slice = function(start, end, type) {
const args = arguments.length;
if (args < 3) {
type = null;
}
return new FakeBlob(
this.data.slice(start, args > 1 ? end : this.data.length), type
);
};
FB_proto.toString = function() {
return '[object Blob]';
};
return FakeBlobBuilder;
}(self);

View File

@ -7,17 +7,17 @@ ForceAtlas2(https://medialab.sciencespo.fr/publications/Jacomy_Heymann_Venturini
- mode: 'normal'/'linlog' - mode: 'normal'/'linlog'
'normal': normal layout 'normal': normal layout
'linlog': the cluster will be more compact 'linlog': the cluster will be more compact
- pre_overlapping: true/false. - preOverlapping: true/false.
true: prevents node overlapping, result in non-node-overlapping layout true: prevents node overlapping, result in non-node-overlapping layout
false: allows node overlapping. false: allows node overlapping.
- dissuade_hubs: true/false. whether active the dissuade hub mode - dissuadeHubs: true/false. whether active the dissuade hub mode
true: grant authorities(nodes with a high indegree) a more central position than hubs(nodes with a high outdegree) true: grant authorities(nodes with a high indegree) a more central position than hubs(nodes with a high outdegree)
false: without dissuade hub mode false: without dissuade hub mode
- barnes_hut: true/false. Whether active the barnes hut optimization on computing repulsive foces. We implemented the paper 'A hierarchical O(N log N) force-calculation algorithm strategy'(https://www.nature.com/articles/324446a0). - barnesHut: true/false. Whether active the barnes hut optimization on computing repulsive foces. We implemented the paper 'A hierarchical O(N log N) force-calculation algorithm strategy'(https://www.nature.com/articles/324446a0).
- ks: controls the global velocity. Default: 0.1 - ks: controls the global velocity. Default: 0.1
- ksmax: the max global velocity. Default: 10 - ksmax: the max global velocity. Default: 10
- tao: the tolerance for the global swinging. Default: 0.1 - tao: the tolerance for the global swinging. Default: 0.1
- max_iteration: the iteration to terminate the algorithm. We suggest 700 for the graph with less than 50 nodes; 1000 for 100 nodes; Upper than 1500 for more nodes. - maxIteration: the iteration to terminate the algorithm. We suggest 700 for the graph with less than 50 nodes; 1000 for 100 nodes; Upper than 1500 for more nodes.
## use ## use
@ -26,18 +26,18 @@ simple use.
```js ```js
$.getJSON('../../test/fixtures/viralMarketing.json', data => { $.getJSON('../../test/fixtures/viralMarketing.json', data => {
const Plugin = G6.Plugins['layout.forceAtlas2']; const Plugin = G6.Plugins['layout.forceAtlas2'];
const layout_params = { const layoutParams = {
max_iteration: 1500, maxIteration: 1500,
prev_overlapping: true, prevOverlapping: true,
kr: 15, kr: 15,
mode: 'normal', mode: 'normal',
barnes_hut: false, // may be counter-productive on small networks barnesHut: false, // may be counter-productive on small networks
ks: 0.1, ks: 0.1,
dissuade_hubs: false dissuadeHubs: false
}; };
graph = new G6.Graph({ graph = new G6.Graph({
id: 'mountNode', // dom id id: 'mountNode', // dom id
plugins: [new Plugin(layout_params)], plugins: [new Plugin(layoutParams)],
height: 1000, height: 1000,
}); });
graph.read(data); graph.read(data);

View File

@ -77,14 +77,14 @@ class Body {
} }
// returns a new body // returns a new body
add(bo) { add(bo) {
const nenw_mass = this.mass + bo.mass; const nenwMass = this.mass + bo.mass;
const x = (this.rx * this.mass + bo.rx * bo.mass) / nenw_mass; const x = (this.rx * this.mass + bo.rx * bo.mass) / nenwMass;
const y = (this.ry * this.mass + bo.ry * bo.mass) / nenw_mass; const y = (this.ry * this.mass + bo.ry * bo.mass) / nenwMass;
const dg = this.degree + bo.degree; const dg = this.degree + bo.degree;
const params = { const params = {
rx: x, rx: x,
ry: y, ry: y,
mass: nenw_mass, mass: nenwMass,
degree: dg degree: dg
}; };
return new Body(params); return new Body(params);

View File

@ -54,7 +54,7 @@ class Layout {
* whether preventing the node overlapping * whether preventing the node overlapping
* @type {boolean} * @type {boolean}
*/ */
prev_overlapping: false, prevOverlapping: false,
/** /**
* whether active the dissuade hub mode * whether active the dissuade hub mode
@ -62,19 +62,19 @@ class Layout {
* a more central position than hubs (nodes with a high outdegree) * a more central position than hubs (nodes with a high outdegree)
* @type {boolean} * @type {boolean}
*/ */
dissuade_hubs: false, dissuadeHubs: false,
/** /**
* whether active the barnes hut optimization on computing repulsive forces * whether active the barnes hut optimization on computing repulsive forces
* @type {boolean} * @type {boolean}
*/ */
barnes_hut: false, barnesHut: false,
/** /**
* the max iteration number * the max iteration number
* @type {number} * @type {number}
*/ */
max_iteration: 1500, maxIteration: 1500,
/** /**
* control the global velocity * control the global velocity
@ -111,18 +111,18 @@ class Layout {
kr, kr,
kg, kg,
mode, mode,
prev_overlapping, prevOverlapping,
dissuade_hubs, dissuadeHubs,
barnes_hut, barnesHut,
max_iteration, maxIteration,
ks, ks,
ksmax, ksmax,
tao, tao,
onLayoutComplete onLayoutComplete
} = this; } = this;
if (!barnes_hut && nodes.length > 300) barnes_hut = true; if (!barnesHut && nodes.length > 300) barnesHut = true;
else if (barnes_hut && nodes.length <= 300) barnes_hut = false; else if (barnesHut && nodes.length <= 300) barnesHut = false;
const width = this.width ? this.width : graph.getWidth(); const width = this.width ? this.width : graph.getWidth();
const height = this.height ? this.height : graph.getHeight(); const height = this.height ? this.height : graph.getHeight();
@ -142,10 +142,10 @@ class Layout {
kr, kr,
kg, kg,
mode, mode,
prev_overlapping, prevOverlapping,
dissuade_hubs, dissuadeHubs,
barnes_hut, barnesHut,
max_iteration, maxIteration,
ks, ks,
ksmax, ksmax,
tao, tao,
@ -153,38 +153,39 @@ class Layout {
widths widths
}; };
// a loading dom before worker // a loading dom before worker
const loading = document.createElement('div'); const loading = document.createElement('div');
loading.id = 'loading'; loading.id = 'loading';
loading.style.setProperty('background-color', '#fff'); loading.style.setProperty('background-color', '#fff');
loading.style.setProperty('position', 'absolute'); loading.style.setProperty('position', 'absolute');
const parent = graph.getGraphContainer(); const parent = graph.getGraphContainer().parentNode;
loading.style.setProperty('width', parent.width() + 'px'); loading.style.setProperty('width', parent.offsetWidth + 'px');
loading.style.setProperty('height', parent.height() + 'px'); loading.style.setProperty('height', parent.offsetHeight + 'px');
loading.style.setProperty('margin-top', (-parent.height()) + 'px'); loading.style.setProperty('margin-top', (-parent.offsetHeight) + 'px');
loading.style.zIndex = 999;
parent.appendChild(loading); parent.appendChild(loading);
// the loading image // the loading image
const loading_img = document.createElement('img'); const loadingImg = document.createElement('img');
loading_img.src = 'https://gw.alipayobjects.com/zos/rmsportal/mnEmjOmrHbghTsZNeTmI.gif'; loadingImg.src = 'https://gw.alipayobjects.com/zos/rmsportal/mnEmjOmrHbghTsZNeTmI.gif';
loading_img.style.setProperty('width', 120 + 'px'); loadingImg.style.setProperty('width', 200 + 'px');
loading_img.style.setProperty('height', 120 + 'px'); loadingImg.style.setProperty('height', 200 + 'px');
const Cw = 120; const Cw = loadingImg.offsetWidth;
const Pw = parent.width(); const Pw = loading.offsetWidth;
const left = (Pw - Cw) / 2; const left = (Pw - Cw) / 2;
loading_img.style.setProperty('margin-left', left + 'px'); loadingImg.style.setProperty('margin-left', left + 'px');
const Ch = 120; const Ch = loadingImg.offsetHeight;
const Ph = parent.height(); const Ph = loading.offsetHeight;
const top = (Ph - Ch) / 2; const top = (Ph - Ch) / 2;
loading_img.style.setProperty('margin-top', top + 'px'); loadingImg.style.setProperty('margin-top', top + 'px');
loading.appendChild(loading_img); loading.appendChild(loadingImg);
const worker = new Worker();// { type: 'module' } const worker = new Worker();// { type: 'module' }
worker.postMessage(obj); worker.postMessage(obj);
worker.onmessage = function(event) { worker.onmessage = function(event) {
this.nodes = event.data; this.nodes = event.data;
const graph_nodes = graph.getNodes(); const graphNodes = graph.getNodes();
for (let i = 0; i < size; i += 1) { for (let i = 0; i < size; i += 1) {
const model = graph_nodes[i].getModel(); const model = graphNodes[i].getModel();
model.x = this.nodes[i].x; model.x = this.nodes[i].x;
model.y = this.nodes[i].y; model.y = this.nodes[i].y;
} }

View File

@ -8,10 +8,10 @@ onmessage = function(event) {
kr, kr,
kg, kg,
mode, mode,
prev_overlapping, prevOverlapping,
dissuade_hubs, dissuadeHubs,
barnes_hut, barnesHut,
max_iteration, maxIteration,
ks, ks,
ksmax, ksmax,
tao, tao,
@ -30,8 +30,6 @@ onmessage = function(event) {
for (let i = 0; i < size; i += 1) { for (let i = 0; i < size; i += 1) {
idmap[nodes[i].id] = i; idmap[nodes[i].id] = i;
degrees[i] = 0; degrees[i] = 0;
// nodes[i].index = i;
// nodes[i].degree = 0;
nodes[i].x = Math.random() * 1000; nodes[i].x = Math.random() * 1000;
nodes[i].y = Math.random() * 1000; nodes[i].y = Math.random() * 1000;
} }
@ -45,24 +43,20 @@ onmessage = function(event) {
node2 = nodes[j]; node2 = nodes[j];
} }
} }
// // const node1 = graph.find(edges[i].source).getModel();
// // const node2 = graph.find(edges[i].target).getModel();
// nodes[node1.index].degree += 1;
// nodes[node2.index].degree += 1;
degrees[idmap[node1.id]] += 1; degrees[idmap[node1.id]] += 1;
degrees[idmap[node2.id]] += 1; degrees[idmap[node2.id]] += 1;
} }
const kr_prime = 100; const krPrime = 100;
let iter = max_iteration; let iter = maxIteration;
const prevo_iter = 50; const prevoIter = 50;
let Forces = []; let Forces = [];
const pre_Forces = []; const preForces = [];
for (let i = 0; i < size; i += 1) { for (let i = 0; i < size; i += 1) {
Forces[2 * i] = 0; Forces[2 * i] = 0;
Forces[2 * i + 1] = 0; Forces[2 * i + 1] = 0;
if (barnes_hut) { if (barnesHut) {
let params = { let params = {
id: i, id: i,
rx: nodes[i].x, rx: nodes[i].x,
@ -78,22 +72,22 @@ onmessage = function(event) {
do { do {
for (let i = 0; i < size; i += 1) { for (let i = 0; i < size; i += 1) {
pre_Forces[2 * i] = Forces[2 * i]; preForces[2 * i] = Forces[2 * i];
pre_Forces[2 * i + 1] = Forces[2 * i + 1]; preForces[2 * i + 1] = Forces[2 * i + 1];
Forces[2 * i] = 0; Forces[2 * i] = 0;
Forces[2 * i + 1] = 0; Forces[2 * i + 1] = 0;
} }
// // attractive forces, existing on every actual edge // // attractive forces, existing on every actual edge
Forces = getAttrForces(nodes, edges, size, esize, prev_overlapping, dissuade_hubs, mode, iter, prevo_iter, Forces, widths, idmap, degrees); Forces = getAttrForces(nodes, edges, size, esize, prevOverlapping, dissuadeHubs, mode, iter, prevoIter, Forces, widths, idmap, degrees);
// // // repulsive forces and Gravity, existing on every node pair // // // repulsive forces and Gravity, existing on every node pair
// // // if prev_overlapping, using the no-optimized method in the last prevo_iter instead. // // // if prevOverlapping, using the no-optimized method in the last prevoIter instead.
if (barnes_hut && ((prev_overlapping && iter > prevo_iter) || !prev_overlapping)) { if (barnesHut && ((prevOverlapping && iter > prevoIter) || !prevOverlapping)) {
Forces = getOptRepGraForces(nodes, edges, size, esize, prev_overlapping, dissuade_hubs, mode, iter, prevo_iter, Forces, kr, kr_prime, kg, center, bodies, degrees); Forces = getOptRepGraForces(nodes, edges, size, esize, prevOverlapping, dissuadeHubs, mode, iter, prevoIter, Forces, kr, krPrime, kg, center, bodies, degrees);
} else { } else {
Forces = getRepGraForces(nodes, edges, size, esize, prev_overlapping, dissuade_hubs, mode, iter, prevo_iter, Forces, kr, kr_prime, kg, center, widths, degrees); Forces = getRepGraForces(nodes, edges, size, esize, prevOverlapping, dissuadeHubs, mode, iter, prevoIter, Forces, kr, krPrime, kg, center, widths, degrees);
} }
// // update the positions // // update the positions
const res = updatePos(size, nodes, Forces, pre_Forces, SG, ks, ksmax, tao, degrees); const res = updatePos(size, nodes, Forces, preForces, SG, ks, ksmax, tao, degrees);
nodes = res[0]; nodes = res[0];
SG = res[1]; SG = res[1];
iter -= 1; iter -= 1;
@ -101,81 +95,75 @@ onmessage = function(event) {
self.postMessage(nodes); self.postMessage(nodes);
}; };
function getAttrForces(nodes, edges, size, esize, prev_overlapping, dissuade_hubs, mode, iter, prevo_iter, Forces, widths, idmap, degrees) { function getAttrForces(nodes, edges, size, esize, prevOverlapping, dissuadeHubs, mode, iter, prevoIter, Forces, widths, idmap, degrees) {
for (let i = 0; i < esize; i += 1) { for (let i = 0; i < esize; i += 1) {
// const source_node = graph.find(edges[i].source).getModel(); // const sourceNode = graph.find(edges[i].source).getModel();
// const target_node = graph.find(edges[i].target).getModel(); // const targetNode = graph.find(edges[i].target).getModel();
let source_node; let sourceNode;
let target_node; let targetNode;
let source_idx; let sourceIdx;
let target_idx; let targetIdx;
for (let j = 0; j < size; j += 1) { for (let j = 0; j < size; j += 1) {
if (nodes[j].id === edges[i].source) { if (nodes[j].id === edges[i].source) {
source_node = nodes[j]; sourceNode = nodes[j];
source_idx = j; sourceIdx = j;
} else if (nodes[j].id === edges[i].target) { } else if (nodes[j].id === edges[i].target) {
target_node = nodes[j]; targetNode = nodes[j];
target_idx = j; targetIdx = j;
} }
} }
let dir = [ target_node.x - source_node.x, target_node.y - source_node.y ]; let dir = [ targetNode.x - sourceNode.x, targetNode.y - sourceNode.y ];
let eucli_dis = Math.hypot(dir[0], dir[1]); let eucliDis = Math.hypot(dir[0], dir[1]);
eucli_dis = eucli_dis < 0.0001 ? 0.0001 : eucli_dis; eucliDis = eucliDis < 0.0001 ? 0.0001 : eucliDis;
dir[0] = dir[0] / eucli_dis; dir[0] = dir[0] / eucliDis;
dir[1] = dir[1] / eucli_dis; dir[1] = dir[1] / eucliDis;
// the force // the force
if (prev_overlapping && iter < prevo_iter) eucli_dis = eucli_dis - widths[source_idx] - widths[target_idx]; if (prevOverlapping && iter < prevoIter) eucliDis = eucliDis - widths[sourceIdx] - widths[targetIdx];
let Fa1 = eucli_dis; let Fa1 = eucliDis;
let Fa2 = Fa1; let Fa2 = Fa1;
if (mode === 'linlog') { if (mode === 'linlog') {
Fa1 = Math.log(1 + eucli_dis); Fa1 = Math.log(1 + eucliDis);
Fa2 = Fa1; Fa2 = Fa1;
} }
if (dissuade_hubs) { if (dissuadeHubs) {
// Fa1 = eucli_dis / source_node.degree; Fa1 = eucliDis / degrees[sourceIdx];
// Fa2 = eucli_dis / target_node.degree; Fa2 = eucliDis / degrees[targetIdx];
Fa1 = eucli_dis / degrees[source_idx];
Fa2 = eucli_dis / degrees[target_idx];
} }
if (prev_overlapping && iter < prevo_iter && eucli_dis <= 0) { if (prevOverlapping && iter < prevoIter && eucliDis <= 0) {
Fa1 = 0; Fa1 = 0;
Fa2 = 0; Fa2 = 0;
} else if (prev_overlapping && iter < prevo_iter && eucli_dis > 0) { } else if (prevOverlapping && iter < prevoIter && eucliDis > 0) {
Fa1 = eucli_dis; Fa1 = eucliDis;
Fa2 = eucli_dis; Fa2 = eucliDis;
} }
// Forces[2 * source_node.index] += Fa1 * dir[0]; Forces[2 * idmap[sourceNode.id]] += Fa1 * dir[0];
// Forces[2 * target_node.index] -= Fa2 * dir[0]; Forces[2 * idmap[targetNode.id]] -= Fa2 * dir[0];
// Forces[2 * source_node.index + 1] += Fa1 * dir[1]; Forces[2 * idmap[sourceNode.id] + 1] += Fa1 * dir[1];
// Forces[2 * target_node.index + 1] -= Fa2 * dir[1]; Forces[2 * idmap[targetNode.id] + 1] -= Fa2 * dir[1];
Forces[2 * idmap[source_node.id]] += Fa1 * dir[0];
Forces[2 * idmap[target_node.id]] -= Fa2 * dir[0];
Forces[2 * idmap[source_node.id] + 1] += Fa1 * dir[1];
Forces[2 * idmap[target_node.id] + 1] -= Fa2 * dir[1];
dir = null; dir = null;
} }
return Forces; return Forces;
} }
function getRepGraForces(nodes, edges, size, esize, prev_overlapping, dissuade_hubs, mode, iter, prevo_iter, Forces, kr, kr_prime, kg, center, widths, degrees) { function getRepGraForces(nodes, edges, size, esize, prevOverlapping, dissuadeHubs, mode, iter, prevoIter, Forces, kr, krPrime, kg, center, widths, degrees) {
for (let i = 0; i < size; i += 1) { for (let i = 0; i < size; i += 1) {
for (let j = i + 1; j < size; j += 1) { for (let j = i + 1; j < size; j += 1) {
let dir = [ nodes[j].x - nodes[i].x, nodes[j].y - nodes[i].y ]; let dir = [ nodes[j].x - nodes[i].x, nodes[j].y - nodes[i].y ];
let eucli_dis = Math.hypot(dir[0], dir[1]); let eucliDis = Math.hypot(dir[0], dir[1]);
eucli_dis = eucli_dis < 0.0001 ? 0.0001 : eucli_dis; eucliDis = eucliDis < 0.0001 ? 0.0001 : eucliDis;
dir[0] = dir[0] / eucli_dis; dir[0] = dir[0] / eucliDis;
dir[1] = dir[1] / eucli_dis; dir[1] = dir[1] / eucliDis;
if (prev_overlapping && iter < prevo_iter) eucli_dis = eucli_dis - widths[i] - widths[j]; if (prevOverlapping && iter < prevoIter) eucliDis = eucliDis - widths[i] - widths[j];
let Fr = kr * (degrees[i] + 1) * (degrees[j] + 1) / eucli_dis; let Fr = kr * (degrees[i] + 1) * (degrees[j] + 1) / eucliDis;
if (prev_overlapping && iter < prevo_iter && eucli_dis < 0) { if (prevOverlapping && iter < prevoIter && eucliDis < 0) {
Fr = kr_prime * (degrees[i] + 1) * (degrees[j] + 1); Fr = krPrime * (degrees[i] + 1) * (degrees[j] + 1);
} else if (prev_overlapping && iter < prevo_iter && eucli_dis === 0) { } else if (prevOverlapping && iter < prevoIter && eucliDis === 0) {
Fr = 0; Fr = 0;
} else if (prev_overlapping && iter < prevo_iter && eucli_dis > 0) { } else if (prevOverlapping && iter < prevoIter && eucliDis > 0) {
Fr = kr * (degrees[i] + 1) * (degrees[j] + 1) / eucli_dis; Fr = kr * (degrees[i] + 1) * (degrees[j] + 1) / eucliDis;
} }
Forces[2 * i] -= Fr * dir[0]; Forces[2 * i] -= Fr * dir[0];
Forces[2 * j] += Fr * dir[0]; Forces[2 * j] += Fr * dir[0];
@ -186,9 +174,9 @@ function getRepGraForces(nodes, edges, size, esize, prev_overlapping, dissuade_h
// gravity // gravity
let dir = [ nodes[i].x - center.x, nodes[i].y - center.y ]; let dir = [ nodes[i].x - center.x, nodes[i].y - center.y ];
const eucli_dis = Math.hypot(dir[0], dir[1]); const eucliDis = Math.hypot(dir[0], dir[1]);
dir[0] = dir[0] / eucli_dis; dir[0] = dir[0] / eucliDis;
dir[1] = dir[1] / eucli_dis; dir[1] = dir[1] / eucliDis;
const Fg = kg * (degrees[i] + 1); const Fg = kg * (degrees[i] + 1);
Forces[2 * i] -= Fg * dir[0]; Forces[2 * i] -= Fg * dir[0];
Forces[2 * i + 1] -= Fg * dir[1]; Forces[2 * i + 1] -= Fg * dir[1];
@ -197,7 +185,7 @@ function getRepGraForces(nodes, edges, size, esize, prev_overlapping, dissuade_h
return Forces; return Forces;
} }
function getOptRepGraForces(nodes, edges, size, esize, prev_overlapping, dissuade_hubs, mode, iter, prevo_iter, Forces, kr, kr_prime, kg, ct, bodies, degrees) { function getOptRepGraForces(nodes, edges, size, esize, prevOverlapping, dissuadeHubs, mode, iter, prevoIter, Forces, kr, krPrime, kg, ct, bodies, degrees) {
let minx = 9e10, let minx = 9e10,
maxx = -9e10, maxx = -9e10,
miny = 9e10, miny = 9e10,
@ -212,75 +200,75 @@ function getOptRepGraForces(nodes, edges, size, esize, prev_overlapping, dissuad
let width = Math.max(maxx - minx, maxy - miny); let width = Math.max(maxx - minx, maxy - miny);
let quad_params = { let quadParams = {
xmid: (maxx + minx) / 2, xmid: (maxx + minx) / 2,
ymid: (maxy + miny) / 2, ymid: (maxy + miny) / 2,
length: width, length: width,
mass_center: ct, massCenter: ct,
mass: size mass: size
}; };
let quad = new Quad(quad_params); let quad = new Quad(quadParams);
let quad_tree = new QuadTree(quad); let quadTree = new QuadTree(quad);
// build the tree, insert the nodes(quads) into the tree // build the tree, insert the nodes(quads) into the tree
for (let i = 0; i < size; i += 1) { for (let i = 0; i < size; i += 1) {
if (bodies[i].in(quad)) quad_tree.insert(bodies[i]); if (bodies[i].in(quad)) quadTree.insert(bodies[i]);
} }
// update the repulsive forces and the gravity. // update the repulsive forces and the gravity.
for (let i = 0; i < size; i += 1) { for (let i = 0; i < size; i += 1) {
bodies[i].resetForce(); bodies[i].resetForce();
quad_tree.updateForce(bodies[i]); quadTree.updateForce(bodies[i]);
Forces[2 * i] -= bodies[i].fx; Forces[2 * i] -= bodies[i].fx;
Forces[2 * i + 1] -= bodies[i].fy; Forces[2 * i + 1] -= bodies[i].fy;
// gravity // gravity
let dir = [ nodes[i].x - ct.x, nodes[i].y - ct.y ]; let dir = [ nodes[i].x - ct.x, nodes[i].y - ct.y ];
let eucli_dis = Math.hypot(dir[0], dir[1]); let eucliDis = Math.hypot(dir[0], dir[1]);
eucli_dis = eucli_dis < 0.0001 ? 0.0001 : eucli_dis; eucliDis = eucliDis < 0.0001 ? 0.0001 : eucliDis;
dir[0] = dir[0] / eucli_dis; dir[0] = dir[0] / eucliDis;
dir[1] = dir[1] / eucli_dis; dir[1] = dir[1] / eucliDis;
let Fg = kg * (degrees[i] + 1); let Fg = kg * (degrees[i] + 1);
Forces[2 * i] -= Fg * dir[0]; Forces[2 * i] -= Fg * dir[0];
Forces[2 * i + 1] -= Fg * dir[1]; Forces[2 * i + 1] -= Fg * dir[1];
eucli_dis = null; eucliDis = null;
Fg = null; Fg = null;
dir = null; dir = null;
} }
quad_params = null; quadParams = null;
quad = null; quad = null;
quad_tree = null; quadTree = null;
width = null; width = null;
return Forces; return Forces;
} }
function updatePos(size, nodes, Forces, pre_Forces, SG, ks, ksmax, tao, degrees) { function updatePos(size, nodes, Forces, preForces, SG, ks, ksmax, tao, degrees) {
let swgns = []; let swgns = [];
let trans = []; let trans = [];
// swg(G) and tra(G) // swg(G) and tra(G)
let swgG = 0; let swgG = 0;
let traG = 0; let traG = 0;
for (let i = 0; i < size; i += 1) { for (let i = 0; i < size; i += 1) {
const minus = [ Forces[2 * i] - pre_Forces[2 * i], const minus = [ Forces[2 * i] - preForces[2 * i],
Forces[2 * i + 1] - pre_Forces[2 * i + 1] Forces[2 * i + 1] - preForces[2 * i + 1]
]; ];
const minus_norm = Math.hypot(minus[0], minus[1]); const minusNorm = Math.hypot(minus[0], minus[1]);
const add = [ Forces[2 * i] + pre_Forces[2 * i], const add = [ Forces[2 * i] + preForces[2 * i],
Forces[2 * i + 1] + pre_Forces[2 * i + 1] Forces[2 * i + 1] + preForces[2 * i + 1]
]; ];
const add_norm = Math.hypot(add[0], add[1]); const addNorm = Math.hypot(add[0], add[1]);
swgns[i] = minus_norm; swgns[i] = minusNorm;
trans[i] = add_norm / 2; trans[i] = addNorm / 2;
swgG += (degrees[i] + 1) * swgns[i]; swgG += (degrees[i] + 1) * swgns[i];
traG += (degrees[i] + 1) * trans[i]; traG += (degrees[i] + 1) * trans[i];
} }
let pre_SG = SG; let preSG = SG;
SG = tao * traG / swgG; SG = tao * traG / swgG;
if (pre_SG !== 0) { if (preSG !== 0) {
SG = SG > (1.5 * pre_SG) ? (1.5 * pre_SG) : SG; SG = SG > (1.5 * preSG) ? (1.5 * preSG) : SG;
} }
// update the node positions // update the node positions
for (let i = 0; i < size; i += 1) { for (let i = 0; i < size; i += 1) {
@ -289,13 +277,13 @@ function updatePos(size, nodes, Forces, pre_Forces, SG, ks, ksmax, tao, degrees)
absForce = absForce < 0.0001 ? 0.0001 : absForce; absForce = absForce < 0.0001 ? 0.0001 : absForce;
const max = ksmax / absForce; const max = ksmax / absForce;
Sn = Sn > max ? max : Sn; Sn = Sn > max ? max : Sn;
const Dn_x = Sn * Forces[2 * i]; const Dnx = Sn * Forces[2 * i];
const Dn_y = Sn * Forces[2 * i + 1]; const Dny = Sn * Forces[2 * i + 1];
nodes[i].x += Dn_x; nodes[i].x += Dnx;
nodes[i].y += Dn_y; nodes[i].y += Dny;
} }
swgns = null; swgns = null;
trans = null; trans = null;
pre_SG = null; preSG = null;
return [ nodes, SG ]; return [ nodes, SG ];
} }

View File

@ -24,7 +24,7 @@ class Quad {
* the mass center of this quad * the mass center of this quad
* @type {number} * @type {number}
*/ */
this.mass_center = params.mass_center; this.massCenter = params.massCenter;
/** /**
* the mass of this quad * the mass of this quad
* @type {number} * @type {number}

View File

@ -1,94 +0,0 @@
/**
* @fileOverview body
* @author shiwu.wyy@antfin.com
*/
const G6 = require('@antv/g6');
const Util = G6.Util;
// represents a body(a point mass) and its position
class Body {
constructor(options) {
Util.mix(this, {
/**
* the id of this body, the same with the node id
* @type {number}
*/
id: -1,
/**
* the position of this body
* @type {number}
*/
rx: null,
/**
* the position of this body
* @type {number}
*/
ry: null,
/**
* the force acting on this body
* @type {number}
*/
fx: 0,
/**
* the force acting on this body
* @type {number}
*/
fy: 0,
/**
* the mass of this body, =1 for a node
* @type {number}
*/
mass: 1,
/**
* the degree of the node represented by this body
* @type {number}
*/
degree: 1,
/**
* the parameter for repulsive force, = kr
* @type {number}
*/
G: 1
}, options);
}
// returns the euclidean distance
distanceTo(bo) {
const dx = this.rx - bo.rx;
const dy = this.ry - bo.ry;
return Math.hypot(dx, dy);
}
setPos(x, y) {
this.rx = x;
this.ry = y;
}
// resets the forces
resetForce() {
this.fx = 0;
this.fy = 0;
}
addForce(b) {
const dx = b.rx - this.rx;
const dy = b.ry - this.ry;
let dist = Math.hypot(dx, dy);
dist = dist < 0.0001 ? 0.0001 : dist;
// the repulsive defined by force atlas 2
const F = (this.G * (this.degree + 1) * (b.degree + 1)) / dist;
this.fx += F * dx / dist;
this.fy += F * dy / dist;
}
// if quad contains this body
in(quad) {
return quad.contains(this.rx, this.ry);
}
// returns a new body
add(bo) {
const nenw_mass = this.mass + bo.mass;
const x = (this.rx * this.mass + bo.rx * bo.mass) / nenw_mass;
const y = (this.ry * this.mass + bo.ry * bo.mass) / nenw_mass;
const dg = this.degree + bo.degree;
const params = { rx: x, ry: y, mass: nenw_mass, degree: dg };
return new Body(params);
}
}
module.exports = Body;

View File

@ -1,85 +0,0 @@
/**
* @fileOverview quad
* @author shiwu.wyy@antfin.com
*/
const G6 = require('@antv/g6');
const Util = G6.Util;
class Quad {
constructor(options) {
Util.mix(this, {
/**
* the center position of this quad
* @type {number}
*/
xmid: null,
/**
* the center position of this quad
* @type {number}
*/
ymid: null,
/**
* the length of this quad
* @type {number}
*/
length: 0,
/**
* the mass center of this quad
* @type {number}
*/
mass_center: [ 0, 0 ],
/**
* the mass of this quad
* @type {number}
*/
mass: 0
}, options);
}
getLength() {
return this.length;
}
contains(x, y) {
const halfLen = this.length / 2;
return (x <= this.xmid + halfLen &&
x >= this.xmid - halfLen &&
y <= this.ymid + halfLen &&
y >= this.ymid - halfLen);
}
// northwest quadrant
NW() {
const x = this.xmid - this.length / 4;
const y = this.ymid + this.length / 4;
const len = this.length / 2;
const params = { xmid: x, ymid: y, length: len };
const NW = new Quad(params);
return NW;
}
// northeast
NE() {
const x = this.xmid + this.length / 4;
const y = this.ymid + this.length / 4;
const len = this.length / 2;
const params = { xmid: x, ymid: y, length: len };
const NE = new Quad(params);
return NE;
}
// southwest
SW() {
const x = this.xmid - this.length / 4;
const y = this.ymid - this.length / 4;
const len = this.length / 2;
const params = { xmid: x, ymid: y, length: len };
const SW = new Quad(params);
return SW;
}
// southeast
SE() {
const x = this.xmid + this.length / 4;
const y = this.ymid - this.length / 4;
const len = this.length / 2;
const params = { xmid: x, ymid: y, length: len };
const SE = new Quad(params);
return SE;
}
}
module.exports = Quad;

View File

@ -1,95 +0,0 @@
/**
* @fileOverview quadTree
* @author shiwu.wyy@antfin.com
*/
const G6 = require('@antv/g6');
const Util = G6.Util;
class QuadTree {
// each quadtree represents a quadrant and an aggregate body
// that represents all bodies inside the quadrant
constructor(options) {
Util.mix(this, {
/**
* (aggregated) body in this quad
* @type {object}
*/
body: null,
/**
* tree representing the northwest quadrant
* @type {object}
*/
quad: null,
NW: null,
NE: null,
SW: null,
SE: null,
/**
* threshold
* @type {number}
*/
theta: 0.5
}, options);
if (options != null) this.quad = options;
}
// insert a body(node) into the tree
insert(bo) {
// if this node does not contain a body, put the new body bo here
if (this.body == null) {
this.body = bo;
return;
}
// internal node
if (!this._isExternal()) {
// update mass info
this.body = this.body.add(bo);
// insert body into quadrant
this._putBody(bo);
} else { // external node
// divide this region into four children
this.NW = new QuadTree(this.quad.NW());
this.NE = new QuadTree(this.quad.NE());
this.SW = new QuadTree(this.quad.SW());
this.SE = new QuadTree(this.quad.SE());
// insert this body and bo
this._putBody(this.body);
this._putBody(bo);
// update the mass info
this.body = this.body.add(bo);
}
}
// inserts bo into a quad
_putBody(bo) {
if (bo.in(this.quad.NW())) this.NW.insert(bo);
else if (bo.in(this.quad.NE())) this.NE.insert(bo);
else if (bo.in(this.quad.SW())) this.SW.insert(bo);
else if (bo.in(this.quad.SE())) this.SE.insert(bo);
}
_isExternal() {
// four children are null
return (this.NW == null && this.NE == null && this.SW == null && this.SE == null);
}
// update the forces
updateForce(bo) {
if (this.body == null || bo === this.body) {
return;
}
// if the current node is external
if (this._isExternal()) bo.addForce(this.body);
// internal nodes
else {
const s = this.quad.getLength();
const d = this.body.distanceTo(bo);
// b is far enough
if ((s / d) < this.theta) bo.addForce(this.body);
else {
this.NW.updateForce(bo);
this.NE.updateForce(bo);
this.SW.updateForce(bo);
this.SE.updateForce(bo);
}
}
}
}
module.exports = QuadTree;

View File

@ -13,9 +13,9 @@ template.maxSpanningForest for jiuselu graph analyzer, which is a plugin for gra
parameter for this plugin: parameter for this plugin:
- layoutCfg: the configuration for layout. - layoutCfg: the configuration for layout.
- max_iteration: the number of iteration for terminating the layout algorithm - maxIteration: the number of iteration for terminating the layout algorithm
- kg: the gravity parameter for layout. The larger kg, more compact the graph, expecialy for the isolated subgraphs. - kg: the gravity parameter for layout. The larger kg, more compact the graph, expecialy for the isolated subgraphs.
- prev_overlapping: whether preventing the node overlapping - prevOverlapping: whether preventing the node overlapping
- onLayoutComplete: a listener for layout completement. When the layout is complete, the loading div and img disappear. - onLayoutComplete: a listener for layout completement. When the layout is complete, the loading div and img disappear.
- menuCfg: the configuration for menu - menuCfg: the configuration for menu
example: example:
@ -46,10 +46,10 @@ To navigate an item (a node or edge) by id or item, if this item is not in the v
graph.navigate(item); // item or id graph.navigate(item); // item or id
To create the menu which follows the mouse click: To create the menu which follows the mouse click:
graph.createMenu(func, container_id); graph.createMenu(func, containerId);
params: params:
- func is the onclick listener for the li '查看单页分析详情' - func is the onclick listener for the li '查看单页分析详情'
- container_id is the DOM id of the menu container. - containerId is the DOM id of the menu container.
## use ## use
@ -60,9 +60,9 @@ const MaxSpanningForestPlugin = G6.Plugins['template.maxSpanningForest'];
//the instances of plugins //the instances of plugins
const maxSpanningForest = new MaxSpanningForestPlugin({ const maxSpanningForest = new MaxSpanningForestPlugin({
layoutCfg: { layoutCfg: {
max_iteration: 600, maxIteration: 600,
kg: 10, kg: 10,
prev_overlapping: true, prevOverlapping: true,
onLayoutComplete: function () { onLayoutComplete: function () {
const minimap = document.getElementById('minimap'); const minimap = document.getElementById('minimap');
const legend = document.getElementById('legend'); const legend = document.getElementById('legend');

View File

@ -43,10 +43,10 @@ class Plugin {
kr: 120, kr: 120,
kg: 8.0, kg: 8.0,
mode: 'common', mode: 'common',
prev_overlapping: true, prevOverlapping: true,
dissuade_hubs: false, dissuadeHubs: false,
max_iteration: 1000, maxIteration: 1000,
barnes_hut: true, barnesHut: true,
ks: 0.1, ks: 0.1,
ksmax: 10, ksmax: 10,
tao: 0.1, tao: 0.1,
@ -55,13 +55,13 @@ class Plugin {
}, },
fisheye: true, fisheye: true,
menu: null, menu: null,
pre_navi: {}, preNavi: {},
edge_style: { edgeStyle: {
endArrow: true, endArrow: true,
stroke: '#4F7DAB', stroke: '#4F7DAB',
strokeOpacity: 0.65 strokeOpacity: 0.65
}, },
node_style: { nodeStyle: {
stroke: '#696969', stroke: '#696969',
strokeOpacity: 0.4, strokeOpacity: 0.4,
lineWidth: 1 lineWidth: 1
@ -148,21 +148,21 @@ class Plugin {
if (!model.isTreeEdge || typeof model.isTreeEdge === 'undefined') model.shape = 'quadraticCurve'; if (!model.isTreeEdge || typeof model.isTreeEdge === 'undefined') model.shape = 'quadraticCurve';
} }
graph.edge({ graph.edge({
style: this.edge_style style: this.edgeStyle
}); });
graph.node({ graph.node({
label: this.node_label, label: this.node_label,
style: this.node_style style: this.nodeStyle
}); });
} }
activeItem(item) { activeItem(item) {
const graph = this.graph; const graph = this.graph;
const pre_navi = this.pre_navi; const preNavi = this.preNavi;
if (Util.isString(item)) { if (Util.isString(item)) {
item = graph.find(item); item = graph.find(item);
} }
let style = {}; let style = {};
let pre_style = {}; let preStyle = {};
if (item.type === 'node') { if (item.type === 'node') {
style = { style = {
stroke: '#fff', stroke: '#fff',
@ -170,29 +170,29 @@ class Plugin {
shadowColor: '#6a80aa', shadowColor: '#6a80aa',
shadowBlur: 20 shadowBlur: 20
}; };
pre_style = this.node_style; preStyle = this.nodeStyle;
} else if (item.type === 'edge') { } else if (item.type === 'edge') {
style = { style = {
endArrow: true, endArrow: true,
stroke: '#4C7295', stroke: '#4C7295',
strokeOpacity: 1 strokeOpacity: 1
}; };
pre_style = this.edge_style; preStyle = this.edgeStyle;
} else return; } else return;
// unactive the previous navigate node // unactive the previous navigate node
if (pre_navi !== {} && pre_navi !== null && pre_navi !== undefined && pre_navi.item !== null) { if (preNavi !== {} && preNavi !== null && preNavi !== undefined && preNavi.item !== null) {
graph.update(pre_navi.item, { graph.update(preNavi.item, {
style: pre_navi.style style: preNavi.style
}); });
} }
graph.update(item, { graph.update(item, {
style style
}); });
this.pre_navi = { this.preNavi = {
item, item,
style: pre_style style: preStyle
}; };
} }
setListener() { setListener() {
@ -207,7 +207,7 @@ class Plugin {
}); });
graph.on('node:mouseleave', ev => { graph.on('node:mouseleave', ev => {
graph.update(ev.item, { graph.update(ev.item, {
style: this.node_style style: this.nodeStyle
}); });
graph.css({ graph.css({
cursor: '-webkit-grab' cursor: '-webkit-grab'
@ -221,7 +221,7 @@ class Plugin {
}); });
graph.on('edge:mouseleave', ev => { graph.on('edge:mouseleave', ev => {
graph.update(ev.item, { graph.update(ev.item, {
style: this.edge_style style: this.edgeStyle
}); });
}); });
@ -230,9 +230,9 @@ class Plugin {
item item
}) => { }) => {
if (shape && item.isNode) { if (shape && item.isNode) {
const menu_x = item.getModel().x * graph.getMatrix()[0] + graph.getMatrix()[6]; const menuX = item.getModel().x * graph.getMatrix()[0] + graph.getMatrix()[6];
const menu_y = item.getModel().y * graph.getMatrix()[0] + graph.getMatrix()[7]; const menuY = item.getModel().y * graph.getMatrix()[0] + graph.getMatrix()[7];
this.menu.show(item, menu_x, menu_y); this.menu.show(item, menuX, menuY);
graph.draw(); graph.draw();
} else { } else {
this.menu.hide(); this.menu.hide();

View File

@ -9,7 +9,7 @@ const Util = G6.Util;
class Menu { class Menu {
constructor(options) { constructor(options) {
Util.mix(this, { Util.mix(this, {
container_id: '', containerId: '',
clickType: 'in' clickType: 'in'
}, options); }, options);
this.createMenu(); this.createMenu();
@ -26,15 +26,15 @@ class Menu {
createMenu() { createMenu() {
const graph = this.graph; const graph = this.graph;
const menuCfg = this.menuCfg; const menuCfg = this.menuCfg;
const container_id = this.container_id; const containerId = this.containerId;
const menuHtml = `<ul class="menu" style = " const menuHtml = `<ul class="menu" style = "
position: absolute; position: absolute;
display: none;"></ul>`; display: none;"></ul>`;
const menu = Util.createDOM(menuHtml); const menu = Util.createDOM(menuHtml);
let parent = graph.getGraphContainer(); let parent = graph.getGraphContainer();
if (container_id !== '' && container_id !== undefined) { if (containerId !== '' && containerId !== undefined) {
parent = document.getElementById(container_id); parent = document.getElementById(containerId);
} }
parent.appendChild(menu); parent.appendChild(menu);
@ -62,12 +62,12 @@ class Menu {
showSource(node) { showSource(node) {
const graph = this.graph; const graph = this.graph;
const { const {
re_nodes, reNodes,
re_edges reEdges
} = Util.extract(graph, 'in', 1, [ node ]); } = Util.extract(graph, 'in', 1, [ node ]);
graph.highlightSubgraph({ graph.highlightSubgraph({
re_nodes, reNodes,
re_edges reEdges
}); });
// show the hided edge, which is not tree edge and it is in the es // show the hided edge, which is not tree edge and it is in the es
// and the source and targert of the edge are both visible // and the source and targert of the edge are both visible
@ -75,7 +75,7 @@ class Menu {
Util.each(edges, edge => { Util.each(edges, edge => {
if (!edge.isVisible() && !edge.getModel().isTreeEdge && if (!edge.isVisible() && !edge.getModel().isTreeEdge &&
edge.getSource().isVisible() && edge.getTarget().isVisible()) { edge.getSource().isVisible() && edge.getTarget().isVisible()) {
Util.each(re_edges, e => { Util.each(reEdges, e => {
if (edge.id === e.id) { if (edge.id === e.id) {
edge.show(); edge.show();
} }
@ -89,12 +89,12 @@ class Menu {
showTargets(node) { showTargets(node) {
const graph = this.graph; const graph = this.graph;
const { const {
re_nodes, reNodes,
re_edges reEdges
} = Util.extract(graph, 'out', 1, [ node ]); } = Util.extract(graph, 'out', 1, [ node ]);
graph.highlightSubgraph({ graph.highlightSubgraph({
re_nodes, reNodes,
re_edges reEdges
}); });
// show the hided edge, which is not tree edge and it is in the es // show the hided edge, which is not tree edge and it is in the es
// and the source and targert of the edge are both visible // and the source and targert of the edge are both visible
@ -102,7 +102,7 @@ class Menu {
Util.each(edges, edge => { Util.each(edges, edge => {
if (!edge.isVisible() && !edge.getModel().isTreeEdge && if (!edge.isVisible() && !edge.getModel().isTreeEdge &&
edge.getSource().isVisible() && edge.getTarget().isVisible()) { edge.getSource().isVisible() && edge.getTarget().isVisible()) {
Util.each(re_edges, e => { Util.each(reEdges, e => {
if (edge.id === e.id) { if (edge.id === e.id) {
edge.show(); edge.show();
} }
@ -115,12 +115,12 @@ class Menu {
showAll(node) { showAll(node) {
const graph = this.graph; const graph = this.graph;
const { const {
re_nodes, reNodes,
re_edges reEdges
} = Util.extract(graph, 'bi', 1, [ node ]); } = Util.extract(graph, 'bi', 1, [ node ]);
graph.highlightSubgraph({ graph.highlightSubgraph({
re_nodes, reNodes,
re_edges reEdges
}); });
// show the hided edge, which is not tree edge and it is in the es // show the hided edge, which is not tree edge and it is in the es
// and the source and targert of the edge are both visible // and the source and targert of the edge are both visible
@ -128,7 +128,7 @@ class Menu {
Util.each(edges, edge => { Util.each(edges, edge => {
if (!edge.isVisible() && !edge.getModel().isTreeEdge && if (!edge.isVisible() && !edge.getModel().isTreeEdge &&
edge.getSource().isVisible() && edge.getTarget().isVisible()) { edge.getSource().isVisible() && edge.getTarget().isVisible()) {
Util.each(re_edges, e => { Util.each(reEdges, e => {
if (edge.id === e.id) { if (edge.id === e.id) {
edge.show(); edge.show();
} }

View File

@ -9,8 +9,8 @@ const Fisheye = require('./tool');
class Plugin { class Plugin {
constructor(options) { constructor(options) {
Util.mix(this, { Util.mix(this, {
ori_xs: [], oriXs: [],
ori_ys: [] oriYs: []
}, options); }, options);
} }
init() { init() {
@ -23,10 +23,10 @@ class Plugin {
graph.on('mousemove', Util.throttle(ev => { graph.on('mousemove', Util.throttle(ev => {
const nodes = graph.getNodes(); const nodes = graph.getNodes();
const size = nodes.length; const size = nodes.length;
if (this.ori_xs.length !== size) return; if (this.oriXs.length !== size) return;
for (let i = 0; i < size; i += 1) { for (let i = 0; i < size; i += 1) {
nodes[i].getModel().x = this.ori_xs[i]; nodes[i].getModel().x = this.oriXs[i];
nodes[i].getModel().y = this.ori_ys[i]; nodes[i].getModel().y = this.oriYs[i];
} }
fisheye.zoom(ev.x, ev.y); fisheye.zoom(ev.x, ev.y);
graph.updateNodePosition(); graph.updateNodePosition();
@ -39,16 +39,16 @@ class Plugin {
if (ev === undefined || ev.item === undefined) { if (ev === undefined || ev.item === undefined) {
for (let i = 0; i < size; i++) { for (let i = 0; i < size; i++) {
if (nodes[i].getModel().x === undefined) return; if (nodes[i].getModel().x === undefined) return;
this.ori_xs[i] = nodes[i].getModel().x; this.oriXs[i] = nodes[i].getModel().x;
this.ori_ys[i] = nodes[i].getModel().y; this.oriYs[i] = nodes[i].getModel().y;
} }
} else if (ev.item.type !== 'node' || (!ev.updateModel.x && !ev.updateModel.y)) return; } else if (ev.item.type !== 'node' || (!ev.updateModel.x && !ev.updateModel.y)) return;
else { else {
const item = graph.find(ev.originModel.id); const item = graph.find(ev.originModel.id);
for (let i = 0; i < size; i++) { for (let i = 0; i < size; i++) {
if (nodes[i].getModel().id === item.id) { if (nodes[i].getModel().id === item.id) {
this.ori_x[i] = ev.updateModel.x; this.oriXs[i] = ev.updateModel.x;
this.ori_y[i] = ev.updateModel.y; this.oriYs[i] = ev.updateModel.y;
} }
} }
} }

View File

@ -42,9 +42,9 @@ class Tool {
const dist = Math.hypot(node.x - center[0], node.y - center[1]); const dist = Math.hypot(node.x - center[0], node.y - center[1]);
if (dist < radius) { if (dist < radius) {
// take the center as the origin // take the center as the origin
const moved_coords = [ node.x - center[0], node.y - center[1] ]; const movedCoords = [ node.x - center[0], node.y - center[1] ];
// transform to polar coordinates // transform to polar coordinates
const { p, theta } = Rect2Polar(moved_coords[0], moved_coords[1]); const { p, theta } = Rect2Polar(movedCoords[0], movedCoords[1]);
const pf = radius * (((d + 1) * (p / radius)) / (d * (p / radius) + 1)); // after fisheye zooming const pf = radius * (((d + 1) * (p / radius)) / (d * (p / radius) + 1)); // after fisheye zooming
// transform to rect coordinates // transform to rect coordinates
const { x, y } = Polar2Rect(pf, theta); const { x, y } = Polar2Rect(pf, theta);

View File

@ -3,10 +3,10 @@
Highlight a subgraph and weaken the rest of the graph. Highlight a subgraph and weaken the rest of the graph.
interface: interface:
- highlightSubgraph(hl_items) - highlightSubgraph(hlItems)
hightlight a subgraph hightlight a subgraph
params: params:
- hl_items: the items which will be highlighted - hlItems: the items which will be highlighted
- restoreGraph() - restoreGraph()
restore the graph to the un-lighlighted style. restore the graph to the un-lighlighted style.
@ -58,7 +58,7 @@ const graph = new G6.Graph({
graph.read(data); graph.read(data);
const nodes = graph.getNodes(); const nodes = graph.getNodes();
const edges = graph.getEdges(); const edges = graph.getEdges();
const re_nodes = [nodes[0], nodes[1]]; const reNodes = [nodes[0], nodes[1]];
const re_edges = [edges[0]]; const reEdges = [edges[0]];
graph.highlightSubgraph({re_nodes, re_edges}); graph.highlightSubgraph({reNodes, reEdges});
``` ```

View File

@ -35,11 +35,11 @@ class Plugin {
this.graph.restoreGraph = this.restoreGraph; this.graph.restoreGraph = this.restoreGraph;
}); });
} }
highlightSubgraph(hl_items) { highlightSubgraph(hlItems) {
this.restoreGraph(); this.restoreGraph();
// sort the group items // sort the group items
const ns = hl_items.re_nodes; const ns = hlItems.reNodes;
const es = hl_items.re_edges; const es = hlItems.reEdges;
const group = this.getItemGroup(); const group = this.getItemGroup();
const items = this.getItems(); const items = this.getItems();
Util.each(items, i => { Util.each(items, i => {

View File

@ -58,7 +58,8 @@ class Plugin {
* 是否数据对齐 * 是否数据对齐
* @type {boolean} * @type {boolean}
*/ */
nice: true nice: true,
curRange: [ 0, 100 ]
}, { }, {
itemType, itemType,
dim, dim,
@ -145,16 +146,12 @@ class Plugin {
domain = this._trainNumberScale(itemType, data); domain = this._trainNumberScale(itemType, data);
} }
} }
const rangeLength = range.length; const rangeLength = range.length;
const domainLength = domain.length; const domainLength = domain.length;
if (rangeLength !== domainLength) { if (rangeLength !== domainLength && scaleType === 'Category') {
const domainStep = (domain[1] - domain[0]) / (rangeLength - 1); throw new Error('please set the same length of range to the domain!');
Util.each(range, (v, i) => {
domain[i] = domain[0] + i * domainStep;
});
} }
if (domain[0] === domain[1]) { if (domainLength === 2 && domain[0] === domain[1]) {
if (domain[0] > 0) { if (domain[0] > 0) {
domain[0] = 0; domain[0] = 0;
} else if (domain[0] < 0) { } else if (domain[0] < 0) {
@ -176,10 +173,18 @@ class Plugin {
const scaleType = this._getScaleType(data); const scaleType = this._getScaleType(data);
const channel = this.channel; const channel = this.channel;
const graph = this.graph; const graph = this.graph;
const containerId = this.legendCfg.containerId; const containerId = this.legendCfg.containerId;
let legendContainer = this.legendCfg.container; let legendContainer = this.legendCfg.container;
if (containerId === undefined && legendContainer === undefined) { if (legendContainer === undefined) {
legendContainer = Util.createDOM('<div class="legend-container"></div>'); if (containerId === undefined) {
legendContainer = Util.createDOM('<div class="legend-container"></div>');
} else {
legendContainer = document.getElementById(containerId);
if (legendContainer === undefined || legendContainer === null) {
throw new Error('please set the container for the graph !');
}
}
const container = graph.getGraphContainer(); const container = graph.getGraphContainer();
container.appendChild(legendContainer); container.appendChild(legendContainer);
} }
@ -201,27 +206,30 @@ class Plugin {
} }
// the listener to filter nodes and edges // the listener to filter nodes and edges
const slider = legend.get('slider'); const slider = legend.get('slider');
slider.on('sliderchange', Util.throttle(ev => { const domain = this.scale.values;
const domain = this.scale.values; const dim = this.dim;
const cur_range = ev.range; graph.addFilter(item => {
const dim = this.dim; if (item.isNode) {
graph.addFilter(item => { const val = item.model[dim];
if (item.isNode) { const percent = 100 * (val - domain[0]) / (domain[domain.length - 1] - domain[0]);
const val = item.model[dim]; if (percent > this.curRange[1] || percent < this.curRange[0]) {
const percent = 100 * (val - domain[0]) / (domain[domain.length - 1] - domain[0]); return false;
if (percent > cur_range[1] || percent < cur_range[0]) return false;
return true;
} else if (item.isEdge) {
const source_val = item.source.model[dim];
const source_percent = 100 * (source_val - domain[0]) / (domain[domain.length - 1] - domain[0]);
const source_visible = (source_percent <= cur_range[1] && source_percent >= cur_range[0]);
const target_val = item.target.model[dim];
const target_percent = 100 * (target_val - domain[0]) / (domain[domain.length - 1] - domain[0]);
const target_visible = (target_percent <= cur_range[1] && target_percent >= cur_range[0]);
if (!source_visible || !target_visible) return false;
return true;
} }
}); return true;
} else if (item.isEdge) {
const sourceVal = item.source.model[dim];
const sourcePercent = 100 * (sourceVal - domain[0]) / (domain[domain.length - 1] - domain[0]);
const sourceVisible = (sourcePercent <= this.curRange[1] && sourcePercent >= this.curRange[0]);
const targetVal = item.target.model[dim];
const targetPercent = 100 * (targetVal - domain[0]) / (domain[domain.length - 1] - domain[0]);
const targetVisible = (targetPercent <= this.curRange[1] && targetPercent >= this.curRange[0]);
if (!sourceVisible || !targetVisible) return false;
return true;
}
});
slider.on('sliderchange', Util.throttle(ev => {
this.curRange = ev.range;
graph.filter(); graph.filter();
}, 100)); }, 100));
} }
@ -306,14 +314,14 @@ class Plugin {
const items = []; const items = [];
Util.each(range, (val, i) => { Util.each(range, (val, i) => {
const percent = (domain[i] - scale.min) / (scale.max - scale.min); const percent = (domain[i] - scale.min) / (scale.max - scale.min);
let item_text = domain[i]; let itemText = domain[i];
if (legendCfg.formatter !== undefined && legendCfg.formmater !== null) { if (legendCfg.formatter !== undefined && legendCfg.formatter !== null) {
item_text = legendCfg.formatter(domain[i]); itemText = legendCfg.formatter(domain[i]);
} }
items.push({ items.push({
text: domain[i], text: domain[i],
attrValue: val, attrValue: val,
value: item_text, // the number label of the slider value: itemText, // the number label of the slider
scaleValue: percent scaleValue: percent
}); });
}); });
@ -361,14 +369,14 @@ class Plugin {
const items = []; const items = [];
Util.each(range, (val, i) => { Util.each(range, (val, i) => {
const dom = domain[0] + domainStep * i; const dom = domain[0] + domainStep * i;
let item_text = dom; let itemText = dom;
if (legendCfg.formatter !== undefined && legendCfg.formmater !== null) { if (legendCfg.formatter !== undefined && legendCfg.formmater !== null) {
item_text = legendCfg.formatter(dom); itemText = legendCfg.formatter(dom);
} }
items.push({ items.push({
text: dom, text: dom,
attrValue: val, attrValue: val,
value: item_text // the number label of the slider value: itemText // the number label of the slider
}); });
}); });
const cfg = { const cfg = {
@ -407,9 +415,8 @@ class Plugin {
return scale.scale(model[dim]) * 2; return scale.scale(model[dim]) * 2;
} else if (channel === 'color') { } else if (channel === 'color') {
return color.mapping(model[dim])[0]; return color.mapping(model[dim])[0];
} else if (itemType === 'edge' && channel === 'size') {
return scale.scale(model[dim]);
} }
// itemType === 'edge' && channel === 'size'
return scale.scale(model[dim]); return scale.scale(model[dim]);
}); });
} }
@ -419,11 +426,6 @@ class Plugin {
max: domain[domain.length - 1] max: domain[domain.length - 1]
}; };
switch (type) { switch (type) {
case 'Linear':
return Scale.linear({
min: domain[0],
max: domain[domain.length - 1]
});
case 'Category': case 'Category':
return Scale.cat({ return Scale.cat({
values: domain values: domain

View File

@ -1,6 +1,6 @@
## textDisplay ## textDisplay
Hide the labels when the width of the text are 2 times bigger than the parent node. Hide the labels when the width of the text is 2 times bigger than the parent node.
## use ## use

View File

@ -29,10 +29,10 @@ class Plugin {
const label = node.getLabel(); const label = node.getLabel();
const model = node.getModel(); const model = node.getModel();
const labelBox = label.getBBox(); const labelBox = label.getBBox();
const label_width = labelBox.maxX - labelBox.minX; const labelWidth = labelBox.maxX - labelBox.minX;
const node_width = model.size * scale; const nodeWidth = model.size * scale;
if (label_width === 0) return; if (labelWidth === 0) return;
const ratio = label_width / node_width; const ratio = labelWidth / nodeWidth;
if (ratio > 2) { if (ratio > 2) {
label.hide(); label.hide();
} else { } else {

View File

@ -10,58 +10,58 @@ const Util = G6.Util;
const extractSubgraph = { const extractSubgraph = {
extract(graph, type, step, focusNodes) { extract(graph, type, step, focusNodes) {
const re_edges = []; const reEdges = [];
Util.each(focusNodes, fn => { Util.each(focusNodes, fn => {
if (type === 'in') { if (type === 'in') {
const in_edges = fn.getInEdges(); const inEdges = fn.getInEdges();
Util.each(in_edges, ie => { Util.each(inEdges, ie => {
re_edges.push(ie); reEdges.push(ie);
}); });
} else if (type === 'out') { } else if (type === 'out') {
const out_edges = fn.getOutEdges(); const outEdges = fn.getOutEdges();
Util.each(out_edges, oe => { Util.each(outEdges, oe => {
re_edges.push(oe); reEdges.push(oe);
}); });
} else { } else {
const in_edges = fn.getInEdges(); const inEdges = fn.getInEdges();
Util.each(in_edges, ie => { Util.each(inEdges, ie => {
re_edges.push(ie); reEdges.push(ie);
}); });
const out_edges = fn.getOutEdges(); const outEdges = fn.getOutEdges();
Util.each(out_edges, oe => { Util.each(outEdges, oe => {
re_edges.push(oe); reEdges.push(oe);
}); });
} }
}); });
const re_nodes = []; const reNodes = [];
Util.each(focusNodes, fn => { Util.each(focusNodes, fn => {
re_nodes.push(fn); reNodes.push(fn);
Util.each(re_edges, e => { Util.each(reEdges, e => {
const source = e.getSource(); const source = e.getSource();
const target = e.getTarget(); const target = e.getTarget();
if (source.id !== fn.id) re_nodes.push(source); if (source.id !== fn.id) reNodes.push(source);
if (target.id !== fn.id) re_nodes.push(target); if (target.id !== fn.id) reNodes.push(target);
}); });
}); });
return { return {
re_nodes, reNodes,
re_edges reEdges
}; };
}, },
setZIndex(ns, es, min_z) { setZIndex(ns, es, minZ) {
const graph = this.graph; const graph = this.graph;
const nodes = graph.getNodes(); const nodes = graph.getNodes();
const edges = graph.getEdges(); const edges = graph.getEdges();
Util.each(nodes, node => { Util.each(nodes, node => {
Util.each(ns, n => { Util.each(ns, n => {
if (node.id === n.id) node.getGraphicGroup().setSilent('zIndex', min_z + 2); if (node.id === n.id) node.getGraphicGroup().setSilent('zIndex', minZ + 2);
}); });
}); });
Util.each(edges, edge => { Util.each(edges, edge => {
Util.each(es, e => { Util.each(es, e => {
if (edge.id === e.id) edge.getGraphicGroup().setSilent('zIndex', min_z + 1); if (edge.id === e.id) edge.getGraphicGroup().setSilent('zIndex', minZ + 1);
}); });
}); });
graph.getItemGroup().sort(); graph.getItemGroup().sort();

View File

@ -106,7 +106,7 @@ class Graph extends Base {
const cfg = {}; const cfg = {};
Mixins.forEach(Mixin => { Mixins.forEach(Mixin => {
Util.mix(cfg, Mixin.CFG, inputCfg); Util.mix(cfg, Util.cloneDeep(Mixin.CFG), inputCfg);
}); });
super(cfg); super(cfg);
// plugin should init before all // plugin should init before all

View File

@ -1 +1 @@
module.exports = '2.1.0-beta'; module.exports = '2.1.0-beta.3';

View File

@ -3,130 +3,145 @@ const Layout = require('../../../build/plugin.layout.forceAtlas2');
const expect = require('chai').expect; const expect = require('chai').expect;
const Util = G6.Util; const Util = G6.Util;
// document.body.appendChild(Util.createDOM(` document.body.appendChild(Util.createDOM(`
// <div id='mountNode'></div> <div id='mountNode'></div>
// `)); `));
// describe('custom layout test', () => { describe('custom layout test', () => {
// const layout = new Layout(); const originInnerHTML = document.getElementById('mountNode').innerHTML;
// const data = { const layout = new Layout();
// nodes: [{ const data = {
// id: 'node1' nodes: [{
// }, { id: 'node1'
// id: 'node2' }, {
// }, { id: 'node2'
// id: 'node3' }, {
// }], id: 'node3'
// edges: [{ }],
// target: 'node2', edges: [{
// source: 'node1' target: 'node2',
// }, { source: 'node1'
// target: 'node2', }, {
// source: 'node3' target: 'node2',
// }] source: 'node3'
// }; }]
// const graph = new G6.Graph({ };
// container: 'mountNode', const graph = new G6.Graph({
// width: 500, container: 'mountNode',
// height: 500, width: 500,
// plugins: [ layout ] height: 500,
// }); plugins: [ layout ]
});
// graph.read(data); graph.read(data);
// it('layout node positions', () => { it('graph render', () => {
// const node1Model = graph.find('node1').getModel(); expect(document.getElementById('mountNode').innerHTML).not.eql(originInnerHTML);
// expect(node1Model.x).not.eql(undefined); });
// expect(node1Model.y).not.eql(undefined); it('layout node positions', () => {
// }); graph.on('afterlayout', () => {
// it('graph destroy', () => { const node1Model = graph.find('node1').getModel();
// graph.destroy(); expect(node1Model.x).not.eql(undefined);
// }); expect(node1Model.y).not.eql(undefined);
// }); });
});
it('graph destroy', () => {
graph.on('afterlayout', () => {
graph.destroy();
expect(document.getElementById('mountNode').innerHTML).eql(originInnerHTML);
});
});
});
// describe('node nonoverlapping test', () => { describe('node nonoverlapping test', () => {
// const layout = new Layout({ prevOverlapping: true }); const layout = new Layout({ prevOverlapping: true });
// const data = { const data = {
// nodes: [{ nodes: [{
// id: 'node1' id: 'node1'
// }, { }, {
// id: 'node2' id: 'node2'
// }, { }, {
// id: 'node3' id: 'node3'
// }], }],
// edges: [{ edges: [{
// target: 'node2', target: 'node2',
// source: 'node1' source: 'node1'
// }, { }, {
// target: 'node2', target: 'node2',
// source: 'node3' source: 'node3'
// }] }]
// }; };
// const graph = new G6.Graph({ const graph = new G6.Graph({
// container: 'mountNode', container: 'mountNode',
// width: 500, width: 500,
// height: 500, height: 500,
// plugins: [ layout ] plugins: [ layout ]
// }); });
// graph.read(data); graph.read(data);
// it('overlapping', () => { it('overlapping', () => {
// const node1Model = graph.find('node1').getModel(); graph.on('afterlayout', () => {
// const node2Model = graph.find('node2').getModel(); const node1Model = graph.find('node1').getModel();
// const node3Model = graph.find('node3').getModel(); const node2Model = graph.find('node2').getModel();
// const node1BBox = node1Model.getBBox(); const node3Model = graph.find('node3').getModel();
// const node2BBox = node2Model.getBBox(); const node1BBox = node1Model.getBBox();
// const node3BBox = node3Model.getBBox(); const node2BBox = node2Model.getBBox();
// const node1Radius = (node1BBox.maxX - node1BBox.minX) / 2; const node3BBox = node3Model.getBBox();
// const node2Radius = (node2BBox.maxX - node2BBox.minX) / 2; const node1Radius = (node1BBox.maxX - node1BBox.minX) / 2;
// const node3Radius = (node3BBox.maxX - node3BBox.minX) / 2; const node2Radius = (node2BBox.maxX - node2BBox.minX) / 2;
// const dist12 = Math.plot(node1Model.x - node2Model.x, node1Model.y - node2Model.y); const node3Radius = (node3BBox.maxX - node3BBox.minX) / 2;
// const dist23 = Math.plot(node2Model.x - node3Model.x, node2Model.y - node3Model.y); const dist12 = Math.plot(node1Model.x - node2Model.x, node1Model.y - node2Model.y);
// const dist13 = Math.plot(node1Model.x - node3Model.x, node1Model.y - node3Model.y); const dist23 = Math.plot(node2Model.x - node3Model.x, node2Model.y - node3Model.y);
// expect(node1Radius - node2Radius <= dist12).eql(true); const dist13 = Math.plot(node1Model.x - node3Model.x, node1Model.y - node3Model.y);
// expect(node2Radius - node3Radius <= dist23).eql(true); expect(node1Radius - node2Radius <= dist12).eql(true);
// expect(node1Radius - node3Radius <= dist13).eql(true); expect(node2Radius - node3Radius <= dist23).eql(true);
// }); expect(node1Radius - node3Radius <= dist13).eql(true);
// it('layout node positions', () => { });
// const node1Model = graph.find('node1').getModel(); });
// expect(node1Model.x).not.eql(undefined); it('layout node positions', () => {
// expect(node1Model.y).not.eql(undefined); graph.on('afterlayout', () => {
// }); const node1Model = graph.find('node1').getModel();
// it('graph destroy', () => { expect(node1Model.x).not.eql(undefined);
// graph.destroy(); expect(node1Model.y).not.eql(undefined);
// }); });
// }); });
it('graph destroy', () => {
graph.destroy();
});
});
// describe('barnes hut optimiazation test', () => { describe('barnes hut optimiazation test', () => {
// const layout = new Layout(); const layout = new Layout();
// const data = { const data = {
// nodes: [{ nodes: [{
// id: 'node1' id: 'node1'
// }, { }, {
// id: 'node2' id: 'node2'
// }, { }, {
// id: 'node3' id: 'node3'
// }], }],
// edges: [{ edges: [{
// target: 'node2', target: 'node2',
// source: 'node1' source: 'node1'
// }, { }, {
// target: 'node2', target: 'node2',
// source: 'node3' source: 'node3'
// }] }]
// }; };
// const graph = new G6.Graph({ const graph = new G6.Graph({
// container: 'mountNode', container: 'mountNode',
// width: 500, width: 500,
// height: 500, height: 500,
// plugins: [ layout ] plugins: [ layout ]
// }); });
// graph.read(data); graph.read(data);
// it('layout node positions', () => { it('layout node positions', () => {
// const node1Model = graph.find('node1').getModel(); graph.on('afterlayout', () => {
// expect(node1Model.x).not.eql(undefined); const node1Model = graph.find('node1').getModel();
// expect(node1Model.y).not.eql(undefined); expect(node1Model.x).not.eql(undefined);
// }); expect(node1Model.y).not.eql(undefined);
// it('graph destroy', () => { });
// graph.destroy(); });
// }); it('graph destroy', () => {
// }); graph.destroy();
});
});

View File

@ -2,15 +2,18 @@ const G6 = require('../../../src/index');
const Mapper = require('../../../plugins/tool.mapper/'); const Mapper = require('../../../plugins/tool.mapper/');
const expect = require('chai').expect; const expect = require('chai').expect;
const Util = G6.Util; const Util = G6.Util;
const Simulate = require('event-simulate');
document.body.appendChild(Util.createDOM(` document.body.appendChild(Util.createDOM(`
<div> <div>
<div id='mountNode'></div> <div id='mountNode'></div>
<div id="nodeSizeLegend"></div> <div id="nodeSizeLegend"></div>
<div id="nodeColorLegend"></div> <div id="nodeColorLegend"></div>
<div id="nodeColorLegend1"></div>
<div id="nodeColorLegend2"></div>
<div id="nodeColorLegend3"></div>
<div id="nodeColorCatLegend"></div> <div id="nodeColorCatLegend"></div>
<div id="nodeSizeFormatLegend"></div> <div id="nodeSizeFormatLegend"></div>
<div id="sliderChangeTestDiv"></div>
</div> </div>
`)); `));
@ -62,15 +65,61 @@ describe('node size mapper test', () => {
}); });
it('legend destroy', () => { it('legend destroy', () => {
graph.destroy(); graph.destroy();
expect(document.getElementById('nodeSizeLegend').innerHTML).eql(originInnerHTML); expect(document.getElementById('nodeSizeLegend')).eql(null);
}); });
}); });
describe('node color mapper test', () => { describe('node color mapper domain length test', () => {
const originInnerHTML = document.getElementById('nodeColorLegend').innerHTML; const fn = function() {
const nodeSizeMapper = new Mapper('node', 'class', 'color', [ '#ff0000', '#00ff00' ], {
legendCfg: {
layout: 'vertical'
}
});
const data = {
nodes: [{
id: 'node1',
x: 100,
y: 200,
class: 'class1'
}, {
id: 'node2',
x: 300,
y: 200,
class: 'class2'
}, {
id: 'node3',
x: 400,
y: 200,
class: 'class3'
}],
edges: [{
target: 'node2',
source: 'node1'
}]
};
const graph = new G6.Graph({
container: 'mountNode',
width: 500,
height: 500,
plugins: [ nodeSizeMapper ]
});
graph.read(data);
};
it('legend render', () => {
expect(fn).to.Throw();
});
});
describe('node color mapper domian equals 1 test', () => {
const originInnerHTML = document.getElementById('nodeColorLegend1').innerHTML;
const nodeSizeMapper = new Mapper('node', 'weight', 'color', [ '#ff0000', '#00ff00' ], { const nodeSizeMapper = new Mapper('node', 'weight', 'color', [ '#ff0000', '#00ff00' ], {
scaleCfg: {
type: 'pow'
},
legendCfg: { legendCfg: {
containerId: 'nodeColorLegend', containerId: 'nodeColorLegend1',
layout: 'vertical' layout: 'vertical'
} }
}); });
@ -84,7 +133,7 @@ describe('node color mapper test', () => {
id: 'node2', id: 'node2',
x: 300, x: 300,
y: 200, y: 200,
weight: 2 weight: 1
}], }],
edges: [{ edges: [{
target: 'node2', target: 'node2',
@ -99,20 +148,123 @@ describe('node color mapper test', () => {
}); });
graph.read(data); graph.read(data);
it('legend render', () => { it('legend render', () => {
expect(document.getElementById('nodeColorLegend').innerHTML).not.eql(originInnerHTML); expect(document.getElementById('nodeColorLegend1').innerHTML).not.eql(originInnerHTML);
});
it('node color mapper', () => {
const node1Model = graph.find('node1').getModel();
const node2Model = graph.find('node2').getModel();
expect(node1Model.color).eql('#00ff00');
expect(node2Model.color).eql('#00ff00');
});
it('legend destroy', () => {
graph.destroy();
expect(document.getElementById('nodeColorLegend1')).eql(null);
});
});
describe('node color mapper domian equals 0 test', () => {
const originInnerHTML = document.getElementById('nodeColorLegend2').innerHTML;
const nodeSizeMapper = new Mapper('node', 'weight', 'color', [ '#ff0000', '#00ff00' ], {
scaleCfg: {
type: 'pow'
},
legendCfg: {
containerId: 'nodeColorLegend2',
layout: '',
formatter: num => {
return num * num;
}
}
});
const data = {
nodes: [{
id: 'node1',
x: 100,
y: 200,
weight: 0
}, {
id: 'node2',
x: 300,
y: 200,
weight: 0
}],
edges: [{
target: 'node2',
source: 'node1'
}]
};
const graph = new G6.Graph({
container: 'mountNode',
width: 500,
height: 500,
plugins: [ nodeSizeMapper ]
});
graph.read(data);
it('legend render', () => {
expect(document.getElementById('nodeColorLegend2').innerHTML).not.eql(originInnerHTML);
});
it('node color mapper', () => {
const node1Model = graph.find('node1').getModel();
const node2Model = graph.find('node2').getModel();
expect(node1Model.color).eql('#00ff00');
expect(node2Model.color).eql('#00ff00');
});
it('legend destroy', () => {
graph.destroy();
expect(document.getElementById('nodeColorLegend2')).eql(null);
});
});
describe('node color mapper domian equals -1 test', () => {
const originInnerHTML = document.getElementById('nodeColorLegend3').innerHTML;
const nodeSizeMapper = new Mapper('node', 'weight', 'color', [ '#ff0000', '#00ff00' ], {
scaleCfg: {
type: 'pow'
},
legendCfg: {
containerId: 'nodeColorLegend3'
}
});
const data = {
nodes: [{
id: 'node1',
x: 100,
y: 200,
weight: -1
}, {
id: 'node2',
x: 300,
y: 200,
weight: -1
}],
edges: [{
target: 'node2',
source: 'node1'
}]
};
const graph = new G6.Graph({
container: 'mountNode',
width: 500,
height: 500,
plugins: [ nodeSizeMapper ]
});
graph.read(data);
it('legend render', () => {
expect(document.getElementById('nodeColorLegend3').innerHTML).not.eql(originInnerHTML);
}); });
it('node color mapper', () => { it('node color mapper', () => {
const node1Model = graph.find('node1').getModel(); const node1Model = graph.find('node1').getModel();
const node2Model = graph.find('node2').getModel(); const node2Model = graph.find('node2').getModel();
expect(node1Model.color).eql('#ff0000'); expect(node1Model.color).eql('#ff0000');
expect(node2Model.color).eql('#00ff00'); expect(node2Model.color).eql('#ff0000');
}); });
it('legend destroy', () => { it('legend destroy', () => {
graph.destroy(); graph.destroy();
expect(document.getElementById('nodeColorLegend').innerHTML).eql(originInnerHTML); expect(document.getElementById('nodeColorLegend3')).eql(null);
}); });
}); });
describe('edge size mapper test', () => { describe('edge size mapper test', () => {
const edgeSizeMapper = new Mapper('edge', 'weight', 'size', [ 10, 50 ], { const edgeSizeMapper = new Mapper('edge', 'weight', 'size', [ 10, 50 ], {
legendCfg: null legendCfg: null
@ -159,6 +311,53 @@ describe('edge size mapper test', () => {
}); });
}); });
describe('edge size mapper vertical test', () => {
const edgeSizeMapper = new Mapper('edge', 'weight', 'size', [ 10, 50 ], {
legendCfg: {
layout: 'vertical'
}
});
const data = {
nodes: [{
id: 'node1',
x: 100,
y: 200
}, {
id: 'node2',
x: 300,
y: 200
}, {
id: 'node3',
x: 400,
y: 200
}],
edges: [{
target: 'node2',
source: 'node1',
weight: 1
}, {
target: 'node2',
source: 'node3',
weight: 2
}]
};
const graph = new G6.Graph({
container: 'mountNode',
width: 500,
height: 500,
plugins: [ edgeSizeMapper ]
});
graph.read(data);
it('edge size mapper', () => {
const edges = graph.getEdges();
const edge1Model = edges[0].getModel();
const edge2Model = edges[1].getModel();
const size1 = edge1Model.size;
const size2 = edge2Model.size;
expect(size1).eql(10);
expect(size2).eql(50);
});
});
describe('node color category mapper test', () => { describe('node color category mapper test', () => {
const originInnerHTML = document.getElementById('nodeColorCatLegend').innerHTML; const originInnerHTML = document.getElementById('nodeColorCatLegend').innerHTML;
@ -202,12 +401,11 @@ describe('node color category mapper test', () => {
}); });
it('legend destroy', () => { it('legend destroy', () => {
graph.destroy(); graph.destroy();
expect(document.getElementById('nodeColorCatLegend').innerHTML).eql(originInnerHTML); expect(document.getElementById('nodeColorCatLegend')).eql(null);
}); });
}); });
describe('node size mapper with formatter test', () => { describe('node size mapper with formatter test', () => {
const originInnerHTML = document.getElementById('nodeSizeFormatLegend').innerHTML;
const nodeSizeMapper = new Mapper('node', 'weight', 'size', [ 10, 50 ], { const nodeSizeMapper = new Mapper('node', 'weight', 'size', [ 10, 50 ], {
legendCfg: { legendCfg: {
formatter: num => { formatter: num => {
@ -239,9 +437,6 @@ describe('node size mapper with formatter test', () => {
plugins: [ nodeSizeMapper ] plugins: [ nodeSizeMapper ]
}); });
graph.read(data); graph.read(data);
it('legend render', () => {
expect(document.getElementById('nodeSizeFormatLegend').innerHTML).not.eql(originInnerHTML);
});
it('node size mapper', () => { it('node size mapper', () => {
const node1Model = graph.find('node1').getModel(); const node1Model = graph.find('node1').getModel();
const node2Model = graph.find('node2').getModel(); const node2Model = graph.find('node2').getModel();
@ -250,9 +445,45 @@ describe('node size mapper with formatter test', () => {
expect(size1).eql(10); expect(size1).eql(10);
expect(size2).eql(50); expect(size2).eql(50);
}); });
it('legend destroy', () => { });
graph.destroy();
expect(document.getElementById('nodeSizeFormatLegend').innerHTML).eql(originInnerHTML);
describe('container undefined test', () => {
const fn = function() {
const nodeSizeMapper = new Mapper('node', 'weight', 'size', [ 10, 50 ], {
legendCfg: {
containerId: 'undefinedDOM'
}
});
const data = {
nodes: [{
id: 'node1',
x: 100,
y: 200,
weight: 2
}, {
id: 'node2',
x: 300,
y: 200,
weight: 3
}],
edges: [{
target: 'node2',
source: 'node1'
}]
};
const graph = new G6.Graph({
container: 'mountNode',
width: 500,
height: 500,
plugins: [ nodeSizeMapper ]
});
graph.read(data);
};
it('legend render', () => {
expect(fn).to.Throw();
}); });
}); });
@ -268,7 +499,7 @@ describe('slider test', () => {
id: 'node1', id: 'node1',
x: 100, x: 100,
y: 200, y: 200,
weight: 2 weight: 1
}, { }, {
id: 'node2', id: 'node2',
x: 300, x: 300,
@ -286,16 +517,11 @@ describe('slider test', () => {
height: 500, height: 500,
plugins: [ nodeSizeMapper ] plugins: [ nodeSizeMapper ]
}); });
const mouseEventWrapper = graph.getMouseEventWrapper();
graph.read(data); graph.read(data);
it('legend sliderchange', () => { it('legend sliderchange', () => {
const node1Model = graph.find('node1').getModel(); const slider = nodeSizeMapper.legend.get('slider');
const clientPoint = graph.getClientPoint(node1Model); slider.emit('sliderchange', { range: [ 0, 50 ] });
Simulate.simulate(mouseEventWrapper, 'sliderchange', { expect(document.getElementById('sliderChangeTestDiv')).not.eql(undefined);
clientX: clientPoint.x - 50,
clientY: clientPoint.y
});
expect(document.getElementsById('sliderChangeTestDiv')).not.eql(undefined);
}); });
}); });