mirror of
https://gitee.com/antv/g6.git
synced 2024-12-05 05:09:07 +08:00
test: tool.mapper-spec
This commit is contained in:
commit
0bea2f6f29
@ -58,7 +58,7 @@
|
||||
graph = new G6.Graph({
|
||||
id: 'mountNode', // dom id
|
||||
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}),
|
||||
nodeSizeMapper, nodeColorMapper
|
||||
],
|
||||
|
@ -25,11 +25,11 @@
|
||||
const Mapper = G6.Plugins['tool.mapper'];
|
||||
const MaxSpanningForestPlugin = G6.Plugins['template.maxSpanningForest'];
|
||||
//the instances of plugins
|
||||
const maxSpanningForest = new MaxSpanningForestPlugin({
|
||||
let maxSpanningForest = new MaxSpanningForestPlugin({
|
||||
layoutCfg: {
|
||||
max_iteration: 600,
|
||||
maxIteration: 600,
|
||||
kg: 10,
|
||||
prev_overlapping: true,
|
||||
prevOverlapping: true,
|
||||
onLayoutComplete: function () {
|
||||
const minimap = document.getElementById('minimap');
|
||||
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: {
|
||||
containerId: 'legend',
|
||||
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
|
||||
});
|
||||
const nodeColorMapper = new Mapper('node', 'stayTime', 'color', ['#BAE7FF', '#1890FF', '#0050B3'], {
|
||||
let nodeColorMapper = new Mapper('node', 'stayTime', 'color', ['#BAE7FF', '#1890FF', '#0050B3'], {
|
||||
legendCfg: {
|
||||
containerId: 'legend',
|
||||
title: 'Stay Time',
|
||||
layout: 'horizontal'
|
||||
}
|
||||
});
|
||||
const minimapPlugin = new G6.Plugins['tool.minimap']({
|
||||
let minimapPlugin = new G6.Plugins['tool.minimap']({
|
||||
container: 'minimap',
|
||||
width: 180,
|
||||
height: 120
|
||||
@ -67,14 +77,14 @@
|
||||
id: 'mountNode', // dom id
|
||||
fitView: 'autoZoom',
|
||||
plugins: [
|
||||
maxSpanningForest, nodeSizeMapper, nodeColorMapper, edgeSizeMapper, minimapPlugin
|
||||
maxSpanningForest, nodeColorMapper, minimapPlugin, edgeSizeMapper, nodeSizeMapper,
|
||||
],
|
||||
modes: {
|
||||
default: ['panCanvas', 'wheelZoom']
|
||||
},
|
||||
height: 500,
|
||||
});
|
||||
graph.read(data);
|
||||
graph.read(Util.cloneDeep(data));
|
||||
const minimap = document.getElementById('minimap');
|
||||
const legend = document.getElementById('legend');
|
||||
if (minimap !== undefined) minimap.style.display = 'none';
|
||||
|
@ -32,13 +32,13 @@
|
||||
nodes.forEach((node, index) => {
|
||||
if (parseInt(index / col) % 2 === 0) {
|
||||
node.x = (col - index % col) * hgap;
|
||||
node.ori_x = node.x;
|
||||
node.oriX = node.x;
|
||||
} else {
|
||||
node.x = index % col * hgap + hgap;
|
||||
node.ori_x = node.x;
|
||||
node.oriX = node.x;
|
||||
}
|
||||
node.y = parseInt(index / col) * vgap + vgap / 2;
|
||||
node.ori_y = node.y;
|
||||
node.oriY = node.y;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -15,19 +15,19 @@
|
||||
<script>
|
||||
$.getJSON('../../test/fixtures/viralMarketing.json', data => {
|
||||
const Plugin = G6.Plugins['layout.forceAtlas2'];
|
||||
const layout_params = {
|
||||
max_iteration: 1500,
|
||||
prev_overlapping: true,
|
||||
const layoutParams = {
|
||||
maxIteration: 1500,
|
||||
prevOverlapping: true,
|
||||
kr: 15,
|
||||
mode: 'normal',
|
||||
barnes_hut: false, // may be counter-productive on small networks
|
||||
barnesHut: false, // may be counter-productive on small networks
|
||||
ks: 0.1,
|
||||
dissuade_hubs: false
|
||||
dissuadeHubs: false
|
||||
};
|
||||
graph = new G6.Graph({
|
||||
id: 'mountNode', // dom id
|
||||
fitView: 'autoZoom',
|
||||
plugins: [new Plugin(layout_params)],
|
||||
plugins: [new Plugin(layoutParams)],
|
||||
minZoom: 0,
|
||||
modes: {
|
||||
default: ['panCanvas']
|
||||
|
@ -54,11 +54,11 @@
|
||||
graph.read(data);
|
||||
const nodes = graph.getNodes();
|
||||
const edges = graph.getEdges();
|
||||
const re_nodes = [nodes[0], nodes[1]];
|
||||
const re_edges = [edges[0]];
|
||||
const reNodes = [nodes[0], nodes[1]];
|
||||
const reEdges = [edges[0]];
|
||||
graph.highlightSubgraph({
|
||||
re_nodes,
|
||||
re_edges
|
||||
reNodes,
|
||||
reEdges
|
||||
});
|
||||
|
||||
</script>
|
||||
|
@ -20,6 +20,7 @@
|
||||
scale: 0.5
|
||||
}
|
||||
});
|
||||
const nodeSizeMapper = new Mapper('node', 'weight', 'size', [20, 40]);
|
||||
const edgeSizeMapper = new Mapper('edge', 'weight', 'size', [2, 20], {
|
||||
legendCfg: null
|
||||
});
|
||||
@ -28,21 +29,25 @@
|
||||
id: 'node1',
|
||||
x: 100,
|
||||
y: 200,
|
||||
weight: 10,
|
||||
class: 'class_1'
|
||||
}, {
|
||||
id: 'node2',
|
||||
x: 300,
|
||||
y: 200,
|
||||
weight: 2,
|
||||
class: 'class_1'
|
||||
}, {
|
||||
id: 'node3',
|
||||
x: 100,
|
||||
y: 100,
|
||||
weight: 15,
|
||||
class: 'class_2'
|
||||
}, {
|
||||
id: 'node4',
|
||||
x: 300,
|
||||
y: 100,
|
||||
weight: 5,
|
||||
class: 'class_2'
|
||||
}],
|
||||
edges: [{
|
||||
@ -61,7 +66,7 @@
|
||||
};
|
||||
const graph = new G6.Graph({
|
||||
id: 'mountNode', // dom id
|
||||
plugins: [nodeColorMapper, edgeSizeMapper],
|
||||
plugins: [nodeColorMapper, edgeSizeMapper, nodeSizeMapper],
|
||||
height: 1000,
|
||||
});
|
||||
graph.read(data);
|
||||
|
@ -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>
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@antv/g6",
|
||||
"version": "2.1.0-beta",
|
||||
"version": "2.1.0-beta.3",
|
||||
"description": "graph visualization frame work",
|
||||
"main": "build/g6.js",
|
||||
"homepage": "https://github.com/antvis/g6",
|
||||
@ -97,7 +97,7 @@
|
||||
"screenshot": "node ./bin/screenshot.js",
|
||||
"start": "npm run dev",
|
||||
"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",
|
||||
"win-dev": "node ./bin/win-dev.js"
|
||||
},
|
||||
|
@ -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);
|
@ -7,17 +7,17 @@ ForceAtlas2(https://medialab.sciencespo.fr/publications/Jacomy_Heymann_Venturini
|
||||
- mode: 'normal'/'linlog'
|
||||
'normal': normal layout
|
||||
'linlog': the cluster will be more compact
|
||||
- pre_overlapping: true/false.
|
||||
- preOverlapping: true/false.
|
||||
true: prevents node overlapping, result in non-node-overlapping layout
|
||||
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)
|
||||
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
|
||||
- ksmax: the max global velocity. Default: 10
|
||||
- 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
|
||||
|
||||
@ -26,18 +26,18 @@ simple use.
|
||||
```js
|
||||
$.getJSON('../../test/fixtures/viralMarketing.json', data => {
|
||||
const Plugin = G6.Plugins['layout.forceAtlas2'];
|
||||
const layout_params = {
|
||||
max_iteration: 1500,
|
||||
prev_overlapping: true,
|
||||
const layoutParams = {
|
||||
maxIteration: 1500,
|
||||
prevOverlapping: true,
|
||||
kr: 15,
|
||||
mode: 'normal',
|
||||
barnes_hut: false, // may be counter-productive on small networks
|
||||
barnesHut: false, // may be counter-productive on small networks
|
||||
ks: 0.1,
|
||||
dissuade_hubs: false
|
||||
dissuadeHubs: false
|
||||
};
|
||||
graph = new G6.Graph({
|
||||
id: 'mountNode', // dom id
|
||||
plugins: [new Plugin(layout_params)],
|
||||
plugins: [new Plugin(layoutParams)],
|
||||
height: 1000,
|
||||
});
|
||||
graph.read(data);
|
||||
|
@ -77,14 +77,14 @@ class Body {
|
||||
}
|
||||
// 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 nenwMass = this.mass + bo.mass;
|
||||
const x = (this.rx * this.mass + bo.rx * bo.mass) / nenwMass;
|
||||
const y = (this.ry * this.mass + bo.ry * bo.mass) / nenwMass;
|
||||
const dg = this.degree + bo.degree;
|
||||
const params = {
|
||||
rx: x,
|
||||
ry: y,
|
||||
mass: nenw_mass,
|
||||
mass: nenwMass,
|
||||
degree: dg
|
||||
};
|
||||
return new Body(params);
|
||||
|
@ -54,7 +54,7 @@ class Layout {
|
||||
* whether preventing the node overlapping
|
||||
* @type {boolean}
|
||||
*/
|
||||
prev_overlapping: false,
|
||||
prevOverlapping: false,
|
||||
|
||||
/**
|
||||
* whether active the dissuade hub mode
|
||||
@ -62,19 +62,19 @@ class Layout {
|
||||
* a more central position than hubs (nodes with a high outdegree)
|
||||
* @type {boolean}
|
||||
*/
|
||||
dissuade_hubs: false,
|
||||
dissuadeHubs: false,
|
||||
|
||||
/**
|
||||
* whether active the barnes hut optimization on computing repulsive forces
|
||||
* @type {boolean}
|
||||
*/
|
||||
barnes_hut: false,
|
||||
barnesHut: false,
|
||||
|
||||
/**
|
||||
* the max iteration number
|
||||
* @type {number}
|
||||
*/
|
||||
max_iteration: 1500,
|
||||
maxIteration: 1500,
|
||||
|
||||
/**
|
||||
* control the global velocity
|
||||
@ -111,18 +111,18 @@ class Layout {
|
||||
kr,
|
||||
kg,
|
||||
mode,
|
||||
prev_overlapping,
|
||||
dissuade_hubs,
|
||||
barnes_hut,
|
||||
max_iteration,
|
||||
prevOverlapping,
|
||||
dissuadeHubs,
|
||||
barnesHut,
|
||||
maxIteration,
|
||||
ks,
|
||||
ksmax,
|
||||
tao,
|
||||
onLayoutComplete
|
||||
} = this;
|
||||
|
||||
if (!barnes_hut && nodes.length > 300) barnes_hut = true;
|
||||
else if (barnes_hut && nodes.length <= 300) barnes_hut = false;
|
||||
if (!barnesHut && nodes.length > 300) barnesHut = true;
|
||||
else if (barnesHut && nodes.length <= 300) barnesHut = false;
|
||||
|
||||
const width = this.width ? this.width : graph.getWidth();
|
||||
const height = this.height ? this.height : graph.getHeight();
|
||||
@ -142,10 +142,10 @@ class Layout {
|
||||
kr,
|
||||
kg,
|
||||
mode,
|
||||
prev_overlapping,
|
||||
dissuade_hubs,
|
||||
barnes_hut,
|
||||
max_iteration,
|
||||
prevOverlapping,
|
||||
dissuadeHubs,
|
||||
barnesHut,
|
||||
maxIteration,
|
||||
ks,
|
||||
ksmax,
|
||||
tao,
|
||||
@ -153,38 +153,39 @@ class Layout {
|
||||
widths
|
||||
};
|
||||
|
||||
// a loading dom before worker
|
||||
// a loading dom before worker
|
||||
const loading = document.createElement('div');
|
||||
loading.id = 'loading';
|
||||
loading.style.setProperty('background-color', '#fff');
|
||||
loading.style.setProperty('position', 'absolute');
|
||||
const parent = graph.getGraphContainer();
|
||||
loading.style.setProperty('width', parent.width() + 'px');
|
||||
loading.style.setProperty('height', parent.height() + 'px');
|
||||
loading.style.setProperty('margin-top', (-parent.height()) + 'px');
|
||||
const parent = graph.getGraphContainer().parentNode;
|
||||
loading.style.setProperty('width', parent.offsetWidth + 'px');
|
||||
loading.style.setProperty('height', parent.offsetHeight + 'px');
|
||||
loading.style.setProperty('margin-top', (-parent.offsetHeight) + 'px');
|
||||
loading.style.zIndex = 999;
|
||||
parent.appendChild(loading);
|
||||
// the loading image
|
||||
const loading_img = document.createElement('img');
|
||||
loading_img.src = 'https://gw.alipayobjects.com/zos/rmsportal/mnEmjOmrHbghTsZNeTmI.gif';
|
||||
loading_img.style.setProperty('width', 120 + 'px');
|
||||
loading_img.style.setProperty('height', 120 + 'px');
|
||||
const Cw = 120;
|
||||
const Pw = parent.width();
|
||||
const loadingImg = document.createElement('img');
|
||||
loadingImg.src = 'https://gw.alipayobjects.com/zos/rmsportal/mnEmjOmrHbghTsZNeTmI.gif';
|
||||
loadingImg.style.setProperty('width', 200 + 'px');
|
||||
loadingImg.style.setProperty('height', 200 + 'px');
|
||||
const Cw = loadingImg.offsetWidth;
|
||||
const Pw = loading.offsetWidth;
|
||||
const left = (Pw - Cw) / 2;
|
||||
loading_img.style.setProperty('margin-left', left + 'px');
|
||||
const Ch = 120;
|
||||
const Ph = parent.height();
|
||||
loadingImg.style.setProperty('margin-left', left + 'px');
|
||||
const Ch = loadingImg.offsetHeight;
|
||||
const Ph = loading.offsetHeight;
|
||||
const top = (Ph - Ch) / 2;
|
||||
loading_img.style.setProperty('margin-top', top + 'px');
|
||||
loading.appendChild(loading_img);
|
||||
loadingImg.style.setProperty('margin-top', top + 'px');
|
||||
loading.appendChild(loadingImg);
|
||||
|
||||
const worker = new Worker();// { type: 'module' }
|
||||
worker.postMessage(obj);
|
||||
worker.onmessage = function(event) {
|
||||
this.nodes = event.data;
|
||||
const graph_nodes = graph.getNodes();
|
||||
const graphNodes = graph.getNodes();
|
||||
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.y = this.nodes[i].y;
|
||||
}
|
||||
|
@ -8,10 +8,10 @@ onmessage = function(event) {
|
||||
kr,
|
||||
kg,
|
||||
mode,
|
||||
prev_overlapping,
|
||||
dissuade_hubs,
|
||||
barnes_hut,
|
||||
max_iteration,
|
||||
prevOverlapping,
|
||||
dissuadeHubs,
|
||||
barnesHut,
|
||||
maxIteration,
|
||||
ks,
|
||||
ksmax,
|
||||
tao,
|
||||
@ -30,8 +30,6 @@ onmessage = function(event) {
|
||||
for (let i = 0; i < size; i += 1) {
|
||||
idmap[nodes[i].id] = i;
|
||||
degrees[i] = 0;
|
||||
// nodes[i].index = i;
|
||||
// nodes[i].degree = 0;
|
||||
nodes[i].x = Math.random() * 1000;
|
||||
nodes[i].y = Math.random() * 1000;
|
||||
}
|
||||
@ -45,24 +43,20 @@ onmessage = function(event) {
|
||||
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[node2.id]] += 1;
|
||||
}
|
||||
|
||||
const kr_prime = 100;
|
||||
let iter = max_iteration;
|
||||
const prevo_iter = 50;
|
||||
const krPrime = 100;
|
||||
let iter = maxIteration;
|
||||
const prevoIter = 50;
|
||||
let Forces = [];
|
||||
const pre_Forces = [];
|
||||
const preForces = [];
|
||||
for (let i = 0; i < size; i += 1) {
|
||||
Forces[2 * i] = 0;
|
||||
Forces[2 * i + 1] = 0;
|
||||
|
||||
if (barnes_hut) {
|
||||
if (barnesHut) {
|
||||
let params = {
|
||||
id: i,
|
||||
rx: nodes[i].x,
|
||||
@ -78,22 +72,22 @@ onmessage = function(event) {
|
||||
|
||||
do {
|
||||
for (let i = 0; i < size; i += 1) {
|
||||
pre_Forces[2 * i] = Forces[2 * i];
|
||||
pre_Forces[2 * i + 1] = Forces[2 * i + 1];
|
||||
preForces[2 * i] = Forces[2 * i];
|
||||
preForces[2 * i + 1] = Forces[2 * i + 1];
|
||||
Forces[2 * i] = 0;
|
||||
Forces[2 * i + 1] = 0;
|
||||
}
|
||||
// // 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
|
||||
// // // if prev_overlapping, using the no-optimized method in the last prevo_iter instead.
|
||||
if (barnes_hut && ((prev_overlapping && iter > prevo_iter) || !prev_overlapping)) {
|
||||
Forces = getOptRepGraForces(nodes, edges, size, esize, prev_overlapping, dissuade_hubs, mode, iter, prevo_iter, Forces, kr, kr_prime, kg, center, bodies, degrees);
|
||||
// // // if prevOverlapping, using the no-optimized method in the last prevoIter instead.
|
||||
if (barnesHut && ((prevOverlapping && iter > prevoIter) || !prevOverlapping)) {
|
||||
Forces = getOptRepGraForces(nodes, edges, size, esize, prevOverlapping, dissuadeHubs, mode, iter, prevoIter, Forces, kr, krPrime, kg, center, bodies, degrees);
|
||||
} 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
|
||||
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];
|
||||
SG = res[1];
|
||||
iter -= 1;
|
||||
@ -101,81 +95,75 @@ onmessage = function(event) {
|
||||
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) {
|
||||
// const source_node = graph.find(edges[i].source).getModel();
|
||||
// const target_node = graph.find(edges[i].target).getModel();
|
||||
let source_node;
|
||||
let target_node;
|
||||
let source_idx;
|
||||
let target_idx;
|
||||
// const sourceNode = graph.find(edges[i].source).getModel();
|
||||
// const targetNode = graph.find(edges[i].target).getModel();
|
||||
let sourceNode;
|
||||
let targetNode;
|
||||
let sourceIdx;
|
||||
let targetIdx;
|
||||
for (let j = 0; j < size; j += 1) {
|
||||
if (nodes[j].id === edges[i].source) {
|
||||
source_node = nodes[j];
|
||||
source_idx = j;
|
||||
sourceNode = nodes[j];
|
||||
sourceIdx = j;
|
||||
} else if (nodes[j].id === edges[i].target) {
|
||||
target_node = nodes[j];
|
||||
target_idx = j;
|
||||
targetNode = nodes[j];
|
||||
targetIdx = j;
|
||||
}
|
||||
}
|
||||
let dir = [ target_node.x - source_node.x, target_node.y - source_node.y ];
|
||||
let eucli_dis = Math.hypot(dir[0], dir[1]);
|
||||
eucli_dis = eucli_dis < 0.0001 ? 0.0001 : eucli_dis;
|
||||
dir[0] = dir[0] / eucli_dis;
|
||||
dir[1] = dir[1] / eucli_dis;
|
||||
let dir = [ targetNode.x - sourceNode.x, targetNode.y - sourceNode.y ];
|
||||
let eucliDis = Math.hypot(dir[0], dir[1]);
|
||||
eucliDis = eucliDis < 0.0001 ? 0.0001 : eucliDis;
|
||||
dir[0] = dir[0] / eucliDis;
|
||||
dir[1] = dir[1] / eucliDis;
|
||||
// the force
|
||||
if (prev_overlapping && iter < prevo_iter) eucli_dis = eucli_dis - widths[source_idx] - widths[target_idx];
|
||||
let Fa1 = eucli_dis;
|
||||
if (prevOverlapping && iter < prevoIter) eucliDis = eucliDis - widths[sourceIdx] - widths[targetIdx];
|
||||
let Fa1 = eucliDis;
|
||||
let Fa2 = Fa1;
|
||||
if (mode === 'linlog') {
|
||||
Fa1 = Math.log(1 + eucli_dis);
|
||||
Fa1 = Math.log(1 + eucliDis);
|
||||
Fa2 = Fa1;
|
||||
}
|
||||
if (dissuade_hubs) {
|
||||
// Fa1 = eucli_dis / source_node.degree;
|
||||
// Fa2 = eucli_dis / target_node.degree;
|
||||
Fa1 = eucli_dis / degrees[source_idx];
|
||||
Fa2 = eucli_dis / degrees[target_idx];
|
||||
if (dissuadeHubs) {
|
||||
Fa1 = eucliDis / degrees[sourceIdx];
|
||||
Fa2 = eucliDis / degrees[targetIdx];
|
||||
}
|
||||
if (prev_overlapping && iter < prevo_iter && eucli_dis <= 0) {
|
||||
if (prevOverlapping && iter < prevoIter && eucliDis <= 0) {
|
||||
Fa1 = 0;
|
||||
Fa2 = 0;
|
||||
} else if (prev_overlapping && iter < prevo_iter && eucli_dis > 0) {
|
||||
Fa1 = eucli_dis;
|
||||
Fa2 = eucli_dis;
|
||||
} else if (prevOverlapping && iter < prevoIter && eucliDis > 0) {
|
||||
Fa1 = eucliDis;
|
||||
Fa2 = eucliDis;
|
||||
}
|
||||
// Forces[2 * source_node.index] += Fa1 * dir[0];
|
||||
// Forces[2 * target_node.index] -= Fa2 * dir[0];
|
||||
// Forces[2 * source_node.index + 1] += Fa1 * dir[1];
|
||||
// Forces[2 * target_node.index + 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];
|
||||
Forces[2 * idmap[sourceNode.id]] += Fa1 * dir[0];
|
||||
Forces[2 * idmap[targetNode.id]] -= Fa2 * dir[0];
|
||||
Forces[2 * idmap[sourceNode.id] + 1] += Fa1 * dir[1];
|
||||
Forces[2 * idmap[targetNode.id] + 1] -= Fa2 * dir[1];
|
||||
dir = null;
|
||||
}
|
||||
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 j = i + 1; j < size; j += 1) {
|
||||
let dir = [ nodes[j].x - nodes[i].x, nodes[j].y - nodes[i].y ];
|
||||
let eucli_dis = Math.hypot(dir[0], dir[1]);
|
||||
eucli_dis = eucli_dis < 0.0001 ? 0.0001 : eucli_dis;
|
||||
dir[0] = dir[0] / eucli_dis;
|
||||
dir[1] = dir[1] / eucli_dis;
|
||||
let eucliDis = Math.hypot(dir[0], dir[1]);
|
||||
eucliDis = eucliDis < 0.0001 ? 0.0001 : eucliDis;
|
||||
dir[0] = dir[0] / eucliDis;
|
||||
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) {
|
||||
Fr = kr_prime * (degrees[i] + 1) * (degrees[j] + 1);
|
||||
} else if (prev_overlapping && iter < prevo_iter && eucli_dis === 0) {
|
||||
if (prevOverlapping && iter < prevoIter && eucliDis < 0) {
|
||||
Fr = krPrime * (degrees[i] + 1) * (degrees[j] + 1);
|
||||
} else if (prevOverlapping && iter < prevoIter && eucliDis === 0) {
|
||||
Fr = 0;
|
||||
} else if (prev_overlapping && iter < prevo_iter && eucli_dis > 0) {
|
||||
Fr = kr * (degrees[i] + 1) * (degrees[j] + 1) / eucli_dis;
|
||||
} else if (prevOverlapping && iter < prevoIter && eucliDis > 0) {
|
||||
Fr = kr * (degrees[i] + 1) * (degrees[j] + 1) / eucliDis;
|
||||
}
|
||||
Forces[2 * i] -= Fr * dir[0];
|
||||
Forces[2 * j] += Fr * dir[0];
|
||||
@ -186,9 +174,9 @@ function getRepGraForces(nodes, edges, size, esize, prev_overlapping, dissuade_h
|
||||
|
||||
// gravity
|
||||
let dir = [ nodes[i].x - center.x, nodes[i].y - center.y ];
|
||||
const eucli_dis = Math.hypot(dir[0], dir[1]);
|
||||
dir[0] = dir[0] / eucli_dis;
|
||||
dir[1] = dir[1] / eucli_dis;
|
||||
const eucliDis = Math.hypot(dir[0], dir[1]);
|
||||
dir[0] = dir[0] / eucliDis;
|
||||
dir[1] = dir[1] / eucliDis;
|
||||
const Fg = kg * (degrees[i] + 1);
|
||||
Forces[2 * i] -= Fg * dir[0];
|
||||
Forces[2 * i + 1] -= Fg * dir[1];
|
||||
@ -197,7 +185,7 @@ function getRepGraForces(nodes, edges, size, esize, prev_overlapping, dissuade_h
|
||||
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,
|
||||
maxx = -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 quad_params = {
|
||||
let quadParams = {
|
||||
xmid: (maxx + minx) / 2,
|
||||
ymid: (maxy + miny) / 2,
|
||||
length: width,
|
||||
mass_center: ct,
|
||||
massCenter: ct,
|
||||
mass: size
|
||||
};
|
||||
let quad = new Quad(quad_params);
|
||||
let quad_tree = new QuadTree(quad);
|
||||
let quad = new Quad(quadParams);
|
||||
let quadTree = new QuadTree(quad);
|
||||
|
||||
// build the tree, insert the nodes(quads) into the tree
|
||||
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.
|
||||
for (let i = 0; i < size; i += 1) {
|
||||
bodies[i].resetForce();
|
||||
quad_tree.updateForce(bodies[i]);
|
||||
quadTree.updateForce(bodies[i]);
|
||||
Forces[2 * i] -= bodies[i].fx;
|
||||
Forces[2 * i + 1] -= bodies[i].fy;
|
||||
|
||||
// gravity
|
||||
let dir = [ nodes[i].x - ct.x, nodes[i].y - ct.y ];
|
||||
let eucli_dis = Math.hypot(dir[0], dir[1]);
|
||||
eucli_dis = eucli_dis < 0.0001 ? 0.0001 : eucli_dis;
|
||||
dir[0] = dir[0] / eucli_dis;
|
||||
dir[1] = dir[1] / eucli_dis;
|
||||
let eucliDis = Math.hypot(dir[0], dir[1]);
|
||||
eucliDis = eucliDis < 0.0001 ? 0.0001 : eucliDis;
|
||||
dir[0] = dir[0] / eucliDis;
|
||||
dir[1] = dir[1] / eucliDis;
|
||||
let Fg = kg * (degrees[i] + 1);
|
||||
Forces[2 * i] -= Fg * dir[0];
|
||||
Forces[2 * i + 1] -= Fg * dir[1];
|
||||
|
||||
eucli_dis = null;
|
||||
eucliDis = null;
|
||||
Fg = null;
|
||||
dir = null;
|
||||
}
|
||||
quad_params = null;
|
||||
quadParams = null;
|
||||
quad = null;
|
||||
quad_tree = null;
|
||||
quadTree = null;
|
||||
width = null;
|
||||
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 trans = [];
|
||||
// swg(G) and tra(G)
|
||||
let swgG = 0;
|
||||
let traG = 0;
|
||||
for (let i = 0; i < size; i += 1) {
|
||||
const minus = [ Forces[2 * i] - pre_Forces[2 * i],
|
||||
Forces[2 * i + 1] - pre_Forces[2 * i + 1]
|
||||
const minus = [ Forces[2 * i] - preForces[2 * i],
|
||||
Forces[2 * i + 1] - preForces[2 * i + 1]
|
||||
];
|
||||
const minus_norm = Math.hypot(minus[0], minus[1]);
|
||||
const add = [ Forces[2 * i] + pre_Forces[2 * i],
|
||||
Forces[2 * i + 1] + pre_Forces[2 * i + 1]
|
||||
const minusNorm = Math.hypot(minus[0], minus[1]);
|
||||
const add = [ Forces[2 * i] + preForces[2 * i],
|
||||
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;
|
||||
trans[i] = add_norm / 2;
|
||||
swgns[i] = minusNorm;
|
||||
trans[i] = addNorm / 2;
|
||||
|
||||
swgG += (degrees[i] + 1) * swgns[i];
|
||||
traG += (degrees[i] + 1) * trans[i];
|
||||
}
|
||||
|
||||
let pre_SG = SG;
|
||||
let preSG = SG;
|
||||
SG = tao * traG / swgG;
|
||||
if (pre_SG !== 0) {
|
||||
SG = SG > (1.5 * pre_SG) ? (1.5 * pre_SG) : SG;
|
||||
if (preSG !== 0) {
|
||||
SG = SG > (1.5 * preSG) ? (1.5 * preSG) : SG;
|
||||
}
|
||||
// update the node positions
|
||||
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;
|
||||
const max = ksmax / absForce;
|
||||
Sn = Sn > max ? max : Sn;
|
||||
const Dn_x = Sn * Forces[2 * i];
|
||||
const Dn_y = Sn * Forces[2 * i + 1];
|
||||
nodes[i].x += Dn_x;
|
||||
nodes[i].y += Dn_y;
|
||||
const Dnx = Sn * Forces[2 * i];
|
||||
const Dny = Sn * Forces[2 * i + 1];
|
||||
nodes[i].x += Dnx;
|
||||
nodes[i].y += Dny;
|
||||
}
|
||||
swgns = null;
|
||||
trans = null;
|
||||
pre_SG = null;
|
||||
preSG = null;
|
||||
return [ nodes, SG ];
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ class Quad {
|
||||
* the mass center of this quad
|
||||
* @type {number}
|
||||
*/
|
||||
this.mass_center = params.mass_center;
|
||||
this.massCenter = params.massCenter;
|
||||
/**
|
||||
* the mass of this quad
|
||||
* @type {number}
|
||||
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -13,9 +13,9 @@ template.maxSpanningForest for jiuselu graph analyzer, which is a plugin for gra
|
||||
|
||||
parameter for this plugin:
|
||||
- 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.
|
||||
- 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.
|
||||
- menuCfg: the configuration for menu
|
||||
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
|
||||
|
||||
To create the menu which follows the mouse click:
|
||||
graph.createMenu(func, container_id);
|
||||
graph.createMenu(func, containerId);
|
||||
params:
|
||||
- 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
|
||||
|
||||
@ -60,9 +60,9 @@ const MaxSpanningForestPlugin = G6.Plugins['template.maxSpanningForest'];
|
||||
//the instances of plugins
|
||||
const maxSpanningForest = new MaxSpanningForestPlugin({
|
||||
layoutCfg: {
|
||||
max_iteration: 600,
|
||||
maxIteration: 600,
|
||||
kg: 10,
|
||||
prev_overlapping: true,
|
||||
prevOverlapping: true,
|
||||
onLayoutComplete: function () {
|
||||
const minimap = document.getElementById('minimap');
|
||||
const legend = document.getElementById('legend');
|
||||
|
@ -43,10 +43,10 @@ class Plugin {
|
||||
kr: 120,
|
||||
kg: 8.0,
|
||||
mode: 'common',
|
||||
prev_overlapping: true,
|
||||
dissuade_hubs: false,
|
||||
max_iteration: 1000,
|
||||
barnes_hut: true,
|
||||
prevOverlapping: true,
|
||||
dissuadeHubs: false,
|
||||
maxIteration: 1000,
|
||||
barnesHut: true,
|
||||
ks: 0.1,
|
||||
ksmax: 10,
|
||||
tao: 0.1,
|
||||
@ -55,13 +55,13 @@ class Plugin {
|
||||
},
|
||||
fisheye: true,
|
||||
menu: null,
|
||||
pre_navi: {},
|
||||
edge_style: {
|
||||
preNavi: {},
|
||||
edgeStyle: {
|
||||
endArrow: true,
|
||||
stroke: '#4F7DAB',
|
||||
strokeOpacity: 0.65
|
||||
},
|
||||
node_style: {
|
||||
nodeStyle: {
|
||||
stroke: '#696969',
|
||||
strokeOpacity: 0.4,
|
||||
lineWidth: 1
|
||||
@ -148,21 +148,21 @@ class Plugin {
|
||||
if (!model.isTreeEdge || typeof model.isTreeEdge === 'undefined') model.shape = 'quadraticCurve';
|
||||
}
|
||||
graph.edge({
|
||||
style: this.edge_style
|
||||
style: this.edgeStyle
|
||||
});
|
||||
graph.node({
|
||||
label: this.node_label,
|
||||
style: this.node_style
|
||||
style: this.nodeStyle
|
||||
});
|
||||
}
|
||||
activeItem(item) {
|
||||
const graph = this.graph;
|
||||
const pre_navi = this.pre_navi;
|
||||
const preNavi = this.preNavi;
|
||||
if (Util.isString(item)) {
|
||||
item = graph.find(item);
|
||||
}
|
||||
let style = {};
|
||||
let pre_style = {};
|
||||
let preStyle = {};
|
||||
if (item.type === 'node') {
|
||||
style = {
|
||||
stroke: '#fff',
|
||||
@ -170,29 +170,29 @@ class Plugin {
|
||||
shadowColor: '#6a80aa',
|
||||
shadowBlur: 20
|
||||
};
|
||||
pre_style = this.node_style;
|
||||
preStyle = this.nodeStyle;
|
||||
} else if (item.type === 'edge') {
|
||||
style = {
|
||||
endArrow: true,
|
||||
stroke: '#4C7295',
|
||||
strokeOpacity: 1
|
||||
};
|
||||
pre_style = this.edge_style;
|
||||
preStyle = this.edgeStyle;
|
||||
} else return;
|
||||
|
||||
// unactive the previous navigate node
|
||||
if (pre_navi !== {} && pre_navi !== null && pre_navi !== undefined && pre_navi.item !== null) {
|
||||
graph.update(pre_navi.item, {
|
||||
style: pre_navi.style
|
||||
if (preNavi !== {} && preNavi !== null && preNavi !== undefined && preNavi.item !== null) {
|
||||
graph.update(preNavi.item, {
|
||||
style: preNavi.style
|
||||
});
|
||||
}
|
||||
|
||||
graph.update(item, {
|
||||
style
|
||||
});
|
||||
this.pre_navi = {
|
||||
this.preNavi = {
|
||||
item,
|
||||
style: pre_style
|
||||
style: preStyle
|
||||
};
|
||||
}
|
||||
setListener() {
|
||||
@ -207,7 +207,7 @@ class Plugin {
|
||||
});
|
||||
graph.on('node:mouseleave', ev => {
|
||||
graph.update(ev.item, {
|
||||
style: this.node_style
|
||||
style: this.nodeStyle
|
||||
});
|
||||
graph.css({
|
||||
cursor: '-webkit-grab'
|
||||
@ -221,7 +221,7 @@ class Plugin {
|
||||
});
|
||||
graph.on('edge:mouseleave', ev => {
|
||||
graph.update(ev.item, {
|
||||
style: this.edge_style
|
||||
style: this.edgeStyle
|
||||
});
|
||||
});
|
||||
|
||||
@ -230,9 +230,9 @@ class Plugin {
|
||||
item
|
||||
}) => {
|
||||
if (shape && item.isNode) {
|
||||
const menu_x = item.getModel().x * graph.getMatrix()[0] + graph.getMatrix()[6];
|
||||
const menu_y = item.getModel().y * graph.getMatrix()[0] + graph.getMatrix()[7];
|
||||
this.menu.show(item, menu_x, menu_y);
|
||||
const menuX = item.getModel().x * graph.getMatrix()[0] + graph.getMatrix()[6];
|
||||
const menuY = item.getModel().y * graph.getMatrix()[0] + graph.getMatrix()[7];
|
||||
this.menu.show(item, menuX, menuY);
|
||||
graph.draw();
|
||||
} else {
|
||||
this.menu.hide();
|
||||
|
@ -9,7 +9,7 @@ const Util = G6.Util;
|
||||
class Menu {
|
||||
constructor(options) {
|
||||
Util.mix(this, {
|
||||
container_id: '',
|
||||
containerId: '',
|
||||
clickType: 'in'
|
||||
}, options);
|
||||
this.createMenu();
|
||||
@ -26,15 +26,15 @@ class Menu {
|
||||
createMenu() {
|
||||
const graph = this.graph;
|
||||
const menuCfg = this.menuCfg;
|
||||
const container_id = this.container_id;
|
||||
const containerId = this.containerId;
|
||||
|
||||
const menuHtml = `<ul class="menu" style = "
|
||||
position: absolute;
|
||||
display: none;"></ul>`;
|
||||
const menu = Util.createDOM(menuHtml);
|
||||
let parent = graph.getGraphContainer();
|
||||
if (container_id !== '' && container_id !== undefined) {
|
||||
parent = document.getElementById(container_id);
|
||||
if (containerId !== '' && containerId !== undefined) {
|
||||
parent = document.getElementById(containerId);
|
||||
}
|
||||
parent.appendChild(menu);
|
||||
|
||||
@ -62,12 +62,12 @@ class Menu {
|
||||
showSource(node) {
|
||||
const graph = this.graph;
|
||||
const {
|
||||
re_nodes,
|
||||
re_edges
|
||||
reNodes,
|
||||
reEdges
|
||||
} = Util.extract(graph, 'in', 1, [ node ]);
|
||||
graph.highlightSubgraph({
|
||||
re_nodes,
|
||||
re_edges
|
||||
reNodes,
|
||||
reEdges
|
||||
});
|
||||
// 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
|
||||
@ -75,7 +75,7 @@ class Menu {
|
||||
Util.each(edges, edge => {
|
||||
if (!edge.isVisible() && !edge.getModel().isTreeEdge &&
|
||||
edge.getSource().isVisible() && edge.getTarget().isVisible()) {
|
||||
Util.each(re_edges, e => {
|
||||
Util.each(reEdges, e => {
|
||||
if (edge.id === e.id) {
|
||||
edge.show();
|
||||
}
|
||||
@ -89,12 +89,12 @@ class Menu {
|
||||
showTargets(node) {
|
||||
const graph = this.graph;
|
||||
const {
|
||||
re_nodes,
|
||||
re_edges
|
||||
reNodes,
|
||||
reEdges
|
||||
} = Util.extract(graph, 'out', 1, [ node ]);
|
||||
graph.highlightSubgraph({
|
||||
re_nodes,
|
||||
re_edges
|
||||
reNodes,
|
||||
reEdges
|
||||
});
|
||||
// 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
|
||||
@ -102,7 +102,7 @@ class Menu {
|
||||
Util.each(edges, edge => {
|
||||
if (!edge.isVisible() && !edge.getModel().isTreeEdge &&
|
||||
edge.getSource().isVisible() && edge.getTarget().isVisible()) {
|
||||
Util.each(re_edges, e => {
|
||||
Util.each(reEdges, e => {
|
||||
if (edge.id === e.id) {
|
||||
edge.show();
|
||||
}
|
||||
@ -115,12 +115,12 @@ class Menu {
|
||||
showAll(node) {
|
||||
const graph = this.graph;
|
||||
const {
|
||||
re_nodes,
|
||||
re_edges
|
||||
reNodes,
|
||||
reEdges
|
||||
} = Util.extract(graph, 'bi', 1, [ node ]);
|
||||
graph.highlightSubgraph({
|
||||
re_nodes,
|
||||
re_edges
|
||||
reNodes,
|
||||
reEdges
|
||||
});
|
||||
// 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
|
||||
@ -128,7 +128,7 @@ class Menu {
|
||||
Util.each(edges, edge => {
|
||||
if (!edge.isVisible() && !edge.getModel().isTreeEdge &&
|
||||
edge.getSource().isVisible() && edge.getTarget().isVisible()) {
|
||||
Util.each(re_edges, e => {
|
||||
Util.each(reEdges, e => {
|
||||
if (edge.id === e.id) {
|
||||
edge.show();
|
||||
}
|
||||
|
@ -9,8 +9,8 @@ const Fisheye = require('./tool');
|
||||
class Plugin {
|
||||
constructor(options) {
|
||||
Util.mix(this, {
|
||||
ori_xs: [],
|
||||
ori_ys: []
|
||||
oriXs: [],
|
||||
oriYs: []
|
||||
}, options);
|
||||
}
|
||||
init() {
|
||||
@ -23,10 +23,10 @@ class Plugin {
|
||||
graph.on('mousemove', Util.throttle(ev => {
|
||||
const nodes = graph.getNodes();
|
||||
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) {
|
||||
nodes[i].getModel().x = this.ori_xs[i];
|
||||
nodes[i].getModel().y = this.ori_ys[i];
|
||||
nodes[i].getModel().x = this.oriXs[i];
|
||||
nodes[i].getModel().y = this.oriYs[i];
|
||||
}
|
||||
fisheye.zoom(ev.x, ev.y);
|
||||
graph.updateNodePosition();
|
||||
@ -39,16 +39,16 @@ class Plugin {
|
||||
if (ev === undefined || ev.item === undefined) {
|
||||
for (let i = 0; i < size; i++) {
|
||||
if (nodes[i].getModel().x === undefined) return;
|
||||
this.ori_xs[i] = nodes[i].getModel().x;
|
||||
this.ori_ys[i] = nodes[i].getModel().y;
|
||||
this.oriXs[i] = nodes[i].getModel().x;
|
||||
this.oriYs[i] = nodes[i].getModel().y;
|
||||
}
|
||||
} else if (ev.item.type !== 'node' || (!ev.updateModel.x && !ev.updateModel.y)) return;
|
||||
else {
|
||||
const item = graph.find(ev.originModel.id);
|
||||
for (let i = 0; i < size; i++) {
|
||||
if (nodes[i].getModel().id === item.id) {
|
||||
this.ori_x[i] = ev.updateModel.x;
|
||||
this.ori_y[i] = ev.updateModel.y;
|
||||
this.oriXs[i] = ev.updateModel.x;
|
||||
this.oriYs[i] = ev.updateModel.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,9 +42,9 @@ class Tool {
|
||||
const dist = Math.hypot(node.x - center[0], node.y - center[1]);
|
||||
if (dist < radius) {
|
||||
// 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
|
||||
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
|
||||
// transform to rect coordinates
|
||||
const { x, y } = Polar2Rect(pf, theta);
|
||||
|
@ -3,10 +3,10 @@
|
||||
Highlight a subgraph and weaken the rest of the graph.
|
||||
|
||||
interface:
|
||||
- highlightSubgraph(hl_items)
|
||||
- highlightSubgraph(hlItems)
|
||||
hightlight a subgraph
|
||||
params:
|
||||
- hl_items: the items which will be highlighted
|
||||
- hlItems: the items which will be highlighted
|
||||
|
||||
- restoreGraph()
|
||||
restore the graph to the un-lighlighted style.
|
||||
@ -58,7 +58,7 @@ const graph = new G6.Graph({
|
||||
graph.read(data);
|
||||
const nodes = graph.getNodes();
|
||||
const edges = graph.getEdges();
|
||||
const re_nodes = [nodes[0], nodes[1]];
|
||||
const re_edges = [edges[0]];
|
||||
graph.highlightSubgraph({re_nodes, re_edges});
|
||||
const reNodes = [nodes[0], nodes[1]];
|
||||
const reEdges = [edges[0]];
|
||||
graph.highlightSubgraph({reNodes, reEdges});
|
||||
```
|
@ -35,11 +35,11 @@ class Plugin {
|
||||
this.graph.restoreGraph = this.restoreGraph;
|
||||
});
|
||||
}
|
||||
highlightSubgraph(hl_items) {
|
||||
highlightSubgraph(hlItems) {
|
||||
this.restoreGraph();
|
||||
// sort the group items
|
||||
const ns = hl_items.re_nodes;
|
||||
const es = hl_items.re_edges;
|
||||
const ns = hlItems.reNodes;
|
||||
const es = hlItems.reEdges;
|
||||
const group = this.getItemGroup();
|
||||
const items = this.getItems();
|
||||
Util.each(items, i => {
|
||||
|
@ -58,7 +58,8 @@ class Plugin {
|
||||
* 是否数据对齐
|
||||
* @type {boolean}
|
||||
*/
|
||||
nice: true
|
||||
nice: true,
|
||||
curRange: [ 0, 100 ]
|
||||
}, {
|
||||
itemType,
|
||||
dim,
|
||||
@ -145,16 +146,12 @@ class Plugin {
|
||||
domain = this._trainNumberScale(itemType, data);
|
||||
}
|
||||
}
|
||||
|
||||
const rangeLength = range.length;
|
||||
const domainLength = domain.length;
|
||||
if (rangeLength !== domainLength) {
|
||||
const domainStep = (domain[1] - domain[0]) / (rangeLength - 1);
|
||||
Util.each(range, (v, i) => {
|
||||
domain[i] = domain[0] + i * domainStep;
|
||||
});
|
||||
if (rangeLength !== domainLength && scaleType === 'Category') {
|
||||
throw new Error('please set the same length of range to the domain!');
|
||||
}
|
||||
if (domain[0] === domain[1]) {
|
||||
if (domainLength === 2 && domain[0] === domain[1]) {
|
||||
if (domain[0] > 0) {
|
||||
domain[0] = 0;
|
||||
} else if (domain[0] < 0) {
|
||||
@ -176,10 +173,18 @@ class Plugin {
|
||||
const scaleType = this._getScaleType(data);
|
||||
const channel = this.channel;
|
||||
const graph = this.graph;
|
||||
|
||||
const containerId = this.legendCfg.containerId;
|
||||
let legendContainer = this.legendCfg.container;
|
||||
if (containerId === undefined && legendContainer === undefined) {
|
||||
legendContainer = Util.createDOM('<div class="legend-container"></div>');
|
||||
if (legendContainer === undefined) {
|
||||
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();
|
||||
container.appendChild(legendContainer);
|
||||
}
|
||||
@ -201,27 +206,30 @@ class Plugin {
|
||||
}
|
||||
// the listener to filter nodes and edges
|
||||
const slider = legend.get('slider');
|
||||
slider.on('sliderchange', Util.throttle(ev => {
|
||||
const domain = this.scale.values;
|
||||
const cur_range = ev.range;
|
||||
const dim = this.dim;
|
||||
graph.addFilter(item => {
|
||||
if (item.isNode) {
|
||||
const val = item.model[dim];
|
||||
const percent = 100 * (val - domain[0]) / (domain[domain.length - 1] - domain[0]);
|
||||
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;
|
||||
const domain = this.scale.values;
|
||||
const dim = this.dim;
|
||||
graph.addFilter(item => {
|
||||
if (item.isNode) {
|
||||
const val = item.model[dim];
|
||||
const percent = 100 * (val - domain[0]) / (domain[domain.length - 1] - domain[0]);
|
||||
if (percent > this.curRange[1] || percent < this.curRange[0]) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
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();
|
||||
}, 100));
|
||||
}
|
||||
@ -306,14 +314,14 @@ class Plugin {
|
||||
const items = [];
|
||||
Util.each(range, (val, i) => {
|
||||
const percent = (domain[i] - scale.min) / (scale.max - scale.min);
|
||||
let item_text = domain[i];
|
||||
if (legendCfg.formatter !== undefined && legendCfg.formmater !== null) {
|
||||
item_text = legendCfg.formatter(domain[i]);
|
||||
let itemText = domain[i];
|
||||
if (legendCfg.formatter !== undefined && legendCfg.formatter !== null) {
|
||||
itemText = legendCfg.formatter(domain[i]);
|
||||
}
|
||||
items.push({
|
||||
text: domain[i],
|
||||
attrValue: val,
|
||||
value: item_text, // the number label of the slider
|
||||
value: itemText, // the number label of the slider
|
||||
scaleValue: percent
|
||||
});
|
||||
});
|
||||
@ -361,14 +369,14 @@ class Plugin {
|
||||
const items = [];
|
||||
Util.each(range, (val, i) => {
|
||||
const dom = domain[0] + domainStep * i;
|
||||
let item_text = dom;
|
||||
let itemText = dom;
|
||||
if (legendCfg.formatter !== undefined && legendCfg.formmater !== null) {
|
||||
item_text = legendCfg.formatter(dom);
|
||||
itemText = legendCfg.formatter(dom);
|
||||
}
|
||||
items.push({
|
||||
text: dom,
|
||||
attrValue: val,
|
||||
value: item_text // the number label of the slider
|
||||
value: itemText // the number label of the slider
|
||||
});
|
||||
});
|
||||
const cfg = {
|
||||
@ -407,9 +415,8 @@ class Plugin {
|
||||
return scale.scale(model[dim]) * 2;
|
||||
} else if (channel === 'color') {
|
||||
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]);
|
||||
});
|
||||
}
|
||||
@ -419,11 +426,6 @@ class Plugin {
|
||||
max: domain[domain.length - 1]
|
||||
};
|
||||
switch (type) {
|
||||
case 'Linear':
|
||||
return Scale.linear({
|
||||
min: domain[0],
|
||||
max: domain[domain.length - 1]
|
||||
});
|
||||
case 'Category':
|
||||
return Scale.cat({
|
||||
values: domain
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 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
|
||||
|
@ -29,10 +29,10 @@ class Plugin {
|
||||
const label = node.getLabel();
|
||||
const model = node.getModel();
|
||||
const labelBox = label.getBBox();
|
||||
const label_width = labelBox.maxX - labelBox.minX;
|
||||
const node_width = model.size * scale;
|
||||
if (label_width === 0) return;
|
||||
const ratio = label_width / node_width;
|
||||
const labelWidth = labelBox.maxX - labelBox.minX;
|
||||
const nodeWidth = model.size * scale;
|
||||
if (labelWidth === 0) return;
|
||||
const ratio = labelWidth / nodeWidth;
|
||||
if (ratio > 2) {
|
||||
label.hide();
|
||||
} else {
|
||||
|
@ -10,58 +10,58 @@ const Util = G6.Util;
|
||||
|
||||
const extractSubgraph = {
|
||||
extract(graph, type, step, focusNodes) {
|
||||
const re_edges = [];
|
||||
const reEdges = [];
|
||||
Util.each(focusNodes, fn => {
|
||||
if (type === 'in') {
|
||||
const in_edges = fn.getInEdges();
|
||||
Util.each(in_edges, ie => {
|
||||
re_edges.push(ie);
|
||||
const inEdges = fn.getInEdges();
|
||||
Util.each(inEdges, ie => {
|
||||
reEdges.push(ie);
|
||||
});
|
||||
} else if (type === 'out') {
|
||||
const out_edges = fn.getOutEdges();
|
||||
Util.each(out_edges, oe => {
|
||||
re_edges.push(oe);
|
||||
const outEdges = fn.getOutEdges();
|
||||
Util.each(outEdges, oe => {
|
||||
reEdges.push(oe);
|
||||
});
|
||||
} else {
|
||||
const in_edges = fn.getInEdges();
|
||||
Util.each(in_edges, ie => {
|
||||
re_edges.push(ie);
|
||||
const inEdges = fn.getInEdges();
|
||||
Util.each(inEdges, ie => {
|
||||
reEdges.push(ie);
|
||||
});
|
||||
const out_edges = fn.getOutEdges();
|
||||
Util.each(out_edges, oe => {
|
||||
re_edges.push(oe);
|
||||
const outEdges = fn.getOutEdges();
|
||||
Util.each(outEdges, oe => {
|
||||
reEdges.push(oe);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const re_nodes = [];
|
||||
const reNodes = [];
|
||||
Util.each(focusNodes, fn => {
|
||||
re_nodes.push(fn);
|
||||
Util.each(re_edges, e => {
|
||||
reNodes.push(fn);
|
||||
Util.each(reEdges, e => {
|
||||
const source = e.getSource();
|
||||
const target = e.getTarget();
|
||||
if (source.id !== fn.id) re_nodes.push(source);
|
||||
if (target.id !== fn.id) re_nodes.push(target);
|
||||
if (source.id !== fn.id) reNodes.push(source);
|
||||
if (target.id !== fn.id) reNodes.push(target);
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
re_nodes,
|
||||
re_edges
|
||||
reNodes,
|
||||
reEdges
|
||||
};
|
||||
},
|
||||
setZIndex(ns, es, min_z) {
|
||||
setZIndex(ns, es, minZ) {
|
||||
const graph = this.graph;
|
||||
const nodes = graph.getNodes();
|
||||
const edges = graph.getEdges();
|
||||
Util.each(nodes, node => {
|
||||
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(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();
|
||||
|
@ -106,7 +106,7 @@ class Graph extends Base {
|
||||
const cfg = {};
|
||||
|
||||
Mixins.forEach(Mixin => {
|
||||
Util.mix(cfg, Mixin.CFG, inputCfg);
|
||||
Util.mix(cfg, Util.cloneDeep(Mixin.CFG), inputCfg);
|
||||
});
|
||||
super(cfg);
|
||||
// plugin should init before all
|
||||
|
@ -1 +1 @@
|
||||
module.exports = '2.1.0-beta';
|
||||
module.exports = '2.1.0-beta.3';
|
||||
|
@ -3,130 +3,145 @@ const Layout = require('../../../build/plugin.layout.forceAtlas2');
|
||||
const expect = require('chai').expect;
|
||||
const Util = G6.Util;
|
||||
|
||||
// document.body.appendChild(Util.createDOM(`
|
||||
// <div id='mountNode'></div>
|
||||
// `));
|
||||
document.body.appendChild(Util.createDOM(`
|
||||
<div id='mountNode'></div>
|
||||
`));
|
||||
|
||||
// describe('custom layout test', () => {
|
||||
// const layout = new Layout();
|
||||
// const data = {
|
||||
// nodes: [{
|
||||
// id: 'node1'
|
||||
// }, {
|
||||
// id: 'node2'
|
||||
// }, {
|
||||
// id: 'node3'
|
||||
// }],
|
||||
// edges: [{
|
||||
// target: 'node2',
|
||||
// source: 'node1'
|
||||
// }, {
|
||||
// target: 'node2',
|
||||
// source: 'node3'
|
||||
// }]
|
||||
// };
|
||||
// const graph = new G6.Graph({
|
||||
// container: 'mountNode',
|
||||
// width: 500,
|
||||
// height: 500,
|
||||
// plugins: [ layout ]
|
||||
// });
|
||||
describe('custom layout test', () => {
|
||||
const originInnerHTML = document.getElementById('mountNode').innerHTML;
|
||||
const layout = new Layout();
|
||||
const data = {
|
||||
nodes: [{
|
||||
id: 'node1'
|
||||
}, {
|
||||
id: 'node2'
|
||||
}, {
|
||||
id: 'node3'
|
||||
}],
|
||||
edges: [{
|
||||
target: 'node2',
|
||||
source: 'node1'
|
||||
}, {
|
||||
target: 'node2',
|
||||
source: 'node3'
|
||||
}]
|
||||
};
|
||||
const graph = new G6.Graph({
|
||||
container: 'mountNode',
|
||||
width: 500,
|
||||
height: 500,
|
||||
plugins: [ layout ]
|
||||
});
|
||||
|
||||
// graph.read(data);
|
||||
// it('layout node positions', () => {
|
||||
// const node1Model = graph.find('node1').getModel();
|
||||
// expect(node1Model.x).not.eql(undefined);
|
||||
// expect(node1Model.y).not.eql(undefined);
|
||||
// });
|
||||
// it('graph destroy', () => {
|
||||
// graph.destroy();
|
||||
// });
|
||||
// });
|
||||
graph.read(data);
|
||||
it('graph render', () => {
|
||||
expect(document.getElementById('mountNode').innerHTML).not.eql(originInnerHTML);
|
||||
});
|
||||
it('layout node positions', () => {
|
||||
graph.on('afterlayout', () => {
|
||||
const node1Model = graph.find('node1').getModel();
|
||||
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', () => {
|
||||
// const layout = new Layout({ prevOverlapping: true });
|
||||
// const data = {
|
||||
// nodes: [{
|
||||
// id: 'node1'
|
||||
// }, {
|
||||
// id: 'node2'
|
||||
// }, {
|
||||
// id: 'node3'
|
||||
// }],
|
||||
// edges: [{
|
||||
// target: 'node2',
|
||||
// source: 'node1'
|
||||
// }, {
|
||||
// target: 'node2',
|
||||
// source: 'node3'
|
||||
// }]
|
||||
// };
|
||||
// const graph = new G6.Graph({
|
||||
// container: 'mountNode',
|
||||
// width: 500,
|
||||
// height: 500,
|
||||
// plugins: [ layout ]
|
||||
// });
|
||||
// graph.read(data);
|
||||
// it('overlapping', () => {
|
||||
// const node1Model = graph.find('node1').getModel();
|
||||
// const node2Model = graph.find('node2').getModel();
|
||||
// const node3Model = graph.find('node3').getModel();
|
||||
// const node1BBox = node1Model.getBBox();
|
||||
// const node2BBox = node2Model.getBBox();
|
||||
// const node3BBox = node3Model.getBBox();
|
||||
// const node1Radius = (node1BBox.maxX - node1BBox.minX) / 2;
|
||||
// const node2Radius = (node2BBox.maxX - node2BBox.minX) / 2;
|
||||
// const node3Radius = (node3BBox.maxX - node3BBox.minX) / 2;
|
||||
// const dist12 = Math.plot(node1Model.x - node2Model.x, node1Model.y - node2Model.y);
|
||||
// const dist23 = Math.plot(node2Model.x - node3Model.x, node2Model.y - node3Model.y);
|
||||
// const dist13 = Math.plot(node1Model.x - node3Model.x, node1Model.y - node3Model.y);
|
||||
// expect(node1Radius - node2Radius <= dist12).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);
|
||||
// expect(node1Model.y).not.eql(undefined);
|
||||
// });
|
||||
// it('graph destroy', () => {
|
||||
// graph.destroy();
|
||||
// });
|
||||
// });
|
||||
describe('node nonoverlapping test', () => {
|
||||
const layout = new Layout({ prevOverlapping: true });
|
||||
const data = {
|
||||
nodes: [{
|
||||
id: 'node1'
|
||||
}, {
|
||||
id: 'node2'
|
||||
}, {
|
||||
id: 'node3'
|
||||
}],
|
||||
edges: [{
|
||||
target: 'node2',
|
||||
source: 'node1'
|
||||
}, {
|
||||
target: 'node2',
|
||||
source: 'node3'
|
||||
}]
|
||||
};
|
||||
const graph = new G6.Graph({
|
||||
container: 'mountNode',
|
||||
width: 500,
|
||||
height: 500,
|
||||
plugins: [ layout ]
|
||||
});
|
||||
graph.read(data);
|
||||
it('overlapping', () => {
|
||||
graph.on('afterlayout', () => {
|
||||
const node1Model = graph.find('node1').getModel();
|
||||
const node2Model = graph.find('node2').getModel();
|
||||
const node3Model = graph.find('node3').getModel();
|
||||
const node1BBox = node1Model.getBBox();
|
||||
const node2BBox = node2Model.getBBox();
|
||||
const node3BBox = node3Model.getBBox();
|
||||
const node1Radius = (node1BBox.maxX - node1BBox.minX) / 2;
|
||||
const node2Radius = (node2BBox.maxX - node2BBox.minX) / 2;
|
||||
const node3Radius = (node3BBox.maxX - node3BBox.minX) / 2;
|
||||
const dist12 = Math.plot(node1Model.x - node2Model.x, node1Model.y - node2Model.y);
|
||||
const dist23 = Math.plot(node2Model.x - node3Model.x, node2Model.y - node3Model.y);
|
||||
const dist13 = Math.plot(node1Model.x - node3Model.x, node1Model.y - node3Model.y);
|
||||
expect(node1Radius - node2Radius <= dist12).eql(true);
|
||||
expect(node2Radius - node3Radius <= dist23).eql(true);
|
||||
expect(node1Radius - node3Radius <= dist13).eql(true);
|
||||
});
|
||||
});
|
||||
it('layout node positions', () => {
|
||||
graph.on('afterlayout', () => {
|
||||
const node1Model = graph.find('node1').getModel();
|
||||
expect(node1Model.x).not.eql(undefined);
|
||||
expect(node1Model.y).not.eql(undefined);
|
||||
});
|
||||
});
|
||||
it('graph destroy', () => {
|
||||
graph.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
// describe('barnes hut optimiazation test', () => {
|
||||
// const layout = new Layout();
|
||||
// const data = {
|
||||
// nodes: [{
|
||||
// id: 'node1'
|
||||
// }, {
|
||||
// id: 'node2'
|
||||
// }, {
|
||||
// id: 'node3'
|
||||
// }],
|
||||
// edges: [{
|
||||
// target: 'node2',
|
||||
// source: 'node1'
|
||||
// }, {
|
||||
// target: 'node2',
|
||||
// source: 'node3'
|
||||
// }]
|
||||
// };
|
||||
// const graph = new G6.Graph({
|
||||
// container: 'mountNode',
|
||||
// width: 500,
|
||||
// height: 500,
|
||||
// plugins: [ layout ]
|
||||
// });
|
||||
describe('barnes hut optimiazation test', () => {
|
||||
const layout = new Layout();
|
||||
const data = {
|
||||
nodes: [{
|
||||
id: 'node1'
|
||||
}, {
|
||||
id: 'node2'
|
||||
}, {
|
||||
id: 'node3'
|
||||
}],
|
||||
edges: [{
|
||||
target: 'node2',
|
||||
source: 'node1'
|
||||
}, {
|
||||
target: 'node2',
|
||||
source: 'node3'
|
||||
}]
|
||||
};
|
||||
const graph = new G6.Graph({
|
||||
container: 'mountNode',
|
||||
width: 500,
|
||||
height: 500,
|
||||
plugins: [ layout ]
|
||||
});
|
||||
|
||||
// graph.read(data);
|
||||
// it('layout node positions', () => {
|
||||
// const node1Model = graph.find('node1').getModel();
|
||||
// expect(node1Model.x).not.eql(undefined);
|
||||
// expect(node1Model.y).not.eql(undefined);
|
||||
// });
|
||||
// it('graph destroy', () => {
|
||||
// graph.destroy();
|
||||
// });
|
||||
// });
|
||||
graph.read(data);
|
||||
it('layout node positions', () => {
|
||||
graph.on('afterlayout', () => {
|
||||
const node1Model = graph.find('node1').getModel();
|
||||
expect(node1Model.x).not.eql(undefined);
|
||||
expect(node1Model.y).not.eql(undefined);
|
||||
});
|
||||
});
|
||||
it('graph destroy', () => {
|
||||
graph.destroy();
|
||||
});
|
||||
});
|
||||
|
@ -2,15 +2,18 @@ const G6 = require('../../../src/index');
|
||||
const Mapper = require('../../../plugins/tool.mapper/');
|
||||
const expect = require('chai').expect;
|
||||
const Util = G6.Util;
|
||||
const Simulate = require('event-simulate');
|
||||
|
||||
document.body.appendChild(Util.createDOM(`
|
||||
<div>
|
||||
<div id='mountNode'></div>
|
||||
<div id="nodeSizeLegend"></div>
|
||||
<div id="nodeColorLegend"></div>
|
||||
<div id="nodeColorLegend1"></div>
|
||||
<div id="nodeColorLegend2"></div>
|
||||
<div id="nodeColorLegend3"></div>
|
||||
<div id="nodeColorCatLegend"></div>
|
||||
<div id="nodeSizeFormatLegend"></div>
|
||||
<div id="sliderChangeTestDiv"></div>
|
||||
</div>
|
||||
`));
|
||||
|
||||
@ -62,15 +65,61 @@ describe('node size mapper test', () => {
|
||||
});
|
||||
it('legend destroy', () => {
|
||||
graph.destroy();
|
||||
expect(document.getElementById('nodeSizeLegend').innerHTML).eql(originInnerHTML);
|
||||
expect(document.getElementById('nodeSizeLegend')).eql(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('node color mapper test', () => {
|
||||
const originInnerHTML = document.getElementById('nodeColorLegend').innerHTML;
|
||||
describe('node color mapper domain length test', () => {
|
||||
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' ], {
|
||||
scaleCfg: {
|
||||
type: 'pow'
|
||||
},
|
||||
legendCfg: {
|
||||
containerId: 'nodeColorLegend',
|
||||
containerId: 'nodeColorLegend1',
|
||||
layout: 'vertical'
|
||||
}
|
||||
});
|
||||
@ -84,7 +133,7 @@ describe('node color mapper test', () => {
|
||||
id: 'node2',
|
||||
x: 300,
|
||||
y: 200,
|
||||
weight: 2
|
||||
weight: 1
|
||||
}],
|
||||
edges: [{
|
||||
target: 'node2',
|
||||
@ -99,20 +148,123 @@ describe('node color mapper test', () => {
|
||||
});
|
||||
graph.read(data);
|
||||
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', () => {
|
||||
const node1Model = graph.find('node1').getModel();
|
||||
const node2Model = graph.find('node2').getModel();
|
||||
expect(node1Model.color).eql('#ff0000');
|
||||
expect(node2Model.color).eql('#00ff00');
|
||||
expect(node2Model.color).eql('#ff0000');
|
||||
});
|
||||
it('legend destroy', () => {
|
||||
graph.destroy();
|
||||
expect(document.getElementById('nodeColorLegend').innerHTML).eql(originInnerHTML);
|
||||
expect(document.getElementById('nodeColorLegend3')).eql(null);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('edge size mapper test', () => {
|
||||
const edgeSizeMapper = new Mapper('edge', 'weight', 'size', [ 10, 50 ], {
|
||||
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', () => {
|
||||
const originInnerHTML = document.getElementById('nodeColorCatLegend').innerHTML;
|
||||
@ -202,12 +401,11 @@ describe('node color category mapper test', () => {
|
||||
});
|
||||
it('legend destroy', () => {
|
||||
graph.destroy();
|
||||
expect(document.getElementById('nodeColorCatLegend').innerHTML).eql(originInnerHTML);
|
||||
expect(document.getElementById('nodeColorCatLegend')).eql(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('node size mapper with formatter test', () => {
|
||||
const originInnerHTML = document.getElementById('nodeSizeFormatLegend').innerHTML;
|
||||
const nodeSizeMapper = new Mapper('node', 'weight', 'size', [ 10, 50 ], {
|
||||
legendCfg: {
|
||||
formatter: num => {
|
||||
@ -239,9 +437,6 @@ describe('node size mapper with formatter test', () => {
|
||||
plugins: [ nodeSizeMapper ]
|
||||
});
|
||||
graph.read(data);
|
||||
it('legend render', () => {
|
||||
expect(document.getElementById('nodeSizeFormatLegend').innerHTML).not.eql(originInnerHTML);
|
||||
});
|
||||
it('node size mapper', () => {
|
||||
const node1Model = graph.find('node1').getModel();
|
||||
const node2Model = graph.find('node2').getModel();
|
||||
@ -250,9 +445,45 @@ describe('node size mapper with formatter test', () => {
|
||||
expect(size1).eql(10);
|
||||
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',
|
||||
x: 100,
|
||||
y: 200,
|
||||
weight: 2
|
||||
weight: 1
|
||||
}, {
|
||||
id: 'node2',
|
||||
x: 300,
|
||||
@ -286,16 +517,11 @@ describe('slider test', () => {
|
||||
height: 500,
|
||||
plugins: [ nodeSizeMapper ]
|
||||
});
|
||||
const mouseEventWrapper = graph.getMouseEventWrapper();
|
||||
graph.read(data);
|
||||
|
||||
it('legend sliderchange', () => {
|
||||
const node1Model = graph.find('node1').getModel();
|
||||
const clientPoint = graph.getClientPoint(node1Model);
|
||||
Simulate.simulate(mouseEventWrapper, 'sliderchange', {
|
||||
clientX: clientPoint.x - 50,
|
||||
clientY: clientPoint.y
|
||||
});
|
||||
expect(document.getElementsById('sliderChangeTestDiv')).not.eql(undefined);
|
||||
const slider = nodeSizeMapper.legend.get('slider');
|
||||
slider.emit('sliderchange', { range: [ 0, 50 ] });
|
||||
expect(document.getElementById('sliderChangeTestDiv')).not.eql(undefined);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user