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({
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
],

View File

@ -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';

View File

@ -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;
});
}
});

View File

@ -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']

View File

@ -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>

View File

@ -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);

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",
"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"
},

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'
'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);

View File

@ -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);

View File

@ -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;
}

View File

@ -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 ];
}

View File

@ -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}

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:
- 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');

View File

@ -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();

View File

@ -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();
}

View File

@ -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;
}
}
}

View File

@ -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);

View File

@ -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});
```

View File

@ -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 => {

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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();

View File

@ -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

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 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();
});
});

View File

@ -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);
});
});