Browse Source

- Fixed critical camera zoom bug #1273. #1266, #1255

- unified some methods in the networkUtil
- Fixed initial zoom issues #1252
webworkersNetwork
Alex de Mulder 8 years ago
parent
commit
3f5c71eea3
11 changed files with 3742 additions and 3553 deletions
  1. +1
    -0
      HISTORY.md
  2. +3434
    -3390
      dist/vis.js
  3. +1
    -1
      dist/vis.min.css
  4. +56
    -0
      examples/network/exampleUtil.js
  5. +77
    -0
      lib/network/NetworkUtil.js
  6. +29
    -10
      lib/network/modules/Canvas.js
  7. +5
    -7
      lib/network/modules/CanvasRenderer.js
  8. +24
    -0
      lib/network/modules/LayoutEngine.js
  9. +3
    -1
      lib/network/modules/PhysicsEngine.js
  10. +31
    -84
      lib/network/modules/View.js
  11. +81
    -60
      test/networkTest.html

+ 1
- 0
HISTORY.md View File

@ -7,6 +7,7 @@ http://visjs.org
### Network
- Added German (de) locale. Thanks @Tooa.
- Fixed critical camera zoom bug #1273.
## 2015-08-28, version 4.8.0

+ 3434
- 3390
dist/vis.js
File diff suppressed because it is too large
View File


+ 1
- 1
dist/vis.min.css
File diff suppressed because it is too large
View File


+ 56
- 0
examples/network/exampleUtil.js View File

@ -66,5 +66,61 @@ function getScaleFreeNetwork(nodeCount) {
}
}
return {nodes:nodes, edges:edges};
}
var randomSeed = 764; // Math.round(Math.random()*1000);
function seededRandom() {
var x = Math.sin(randomSeed++) * 10000;
return x - Math.floor(x);
}
function getScaleFreeNetworkSeeded(nodeCount) {
var nodes = [];
var edges = [];
var connectionCount = [];
// randomly create some nodes and edges
for (var i = 0; i < nodeCount; i++) {
nodes.push({
id: i,
label: String(i)
});
connectionCount[i] = 0;
// create edges in a scale-free-network way
if (i == 1) {
var from = i;
var to = 0;
edges.push({
from: from,
to: to
});
connectionCount[from]++;
connectionCount[to]++;
}
else if (i > 1) {
var conn = edges.length * 2;
var rand = Math.floor(seededRandom() * conn);
var cum = 0;
var j = 0;
while (j < connectionCount.length && cum < rand) {
cum += connectionCount[j];
j++;
}
var from = i;
var to = j;
edges.push({
from: from,
to: to
});
connectionCount[from]++;
connectionCount[to]++;
}
}
return {nodes:nodes, edges:edges};
}

+ 77
- 0
lib/network/NetworkUtil.js View File

@ -0,0 +1,77 @@
class NetworkUtil {
constructor() {}
/**
* Find the center position of the network considering the bounding boxes
* @private
*/
static _getRange(allNodes, specificNodes = []) {
var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node;
if (specificNodes.length > 0) {
for (var i = 0; i < specificNodes.length; i++) {
node = allNodes[specificNodes[i]];
if (minX > node.shape.boundingBox.left) {
minX = node.shape.boundingBox.left;
}
if (maxX < node.shape.boundingBox.right) {
maxX = node.shape.boundingBox.right;
}
if (minY > node.shape.boundingBox.top) {
minY = node.shape.boundingBox.top;
} // top is negative, bottom is positive
if (maxY < node.shape.boundingBox.bottom) {
maxY = node.shape.boundingBox.bottom;
} // top is negative, bottom is positive
}
}
if (minX === 1e9 && maxX === -1e9 && minY === 1e9 && maxY === -1e9) {
minY = 0, maxY = 0, minX = 0, maxX = 0;
}
return {minX: minX, maxX: maxX, minY: minY, maxY: maxY};
}
/**
* Find the center position of the network
* @private
*/
static _getRangeCore(allNodes, specificNodes = []) {
var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node;
if (specificNodes.length > 0) {
for (var i = 0; i < specificNodes.length; i++) {
node = allNodes[specificNodes[i]];
if (minX > node.x) {
minX = node.x;
}
if (maxX < node.x) {
maxX = node.x;
}
if (minY > node.y) {
minY = node.y;
} // top is negative, bottom is positive
if (maxY < node.y) {
maxY = node.y;
} // top is negative, bottom is positive
}
}
if (minX === 1e9 && maxX === -1e9 && minY === 1e9 && maxY === -1e9) {
minY = 0, maxY = 0, minX = 0, maxX = 0;
}
return {minX: minX, maxX: maxX, minY: minY, maxY: maxY};
}
/**
* @param {object} range = {minX: minX, maxX: maxX, minY: minY, maxY: maxY};
* @returns {{x: number, y: number}}
* @private
*/
static _findCenter(range) {
return {x: (0.5 * (range.maxX + range.minX)),
y: (0.5 * (range.maxY + range.minY))};
}
}
export default NetworkUtil;

+ 29
- 10
lib/network/modules/Canvas.js View File

@ -87,10 +87,10 @@ class Canvas {
* Get and store the cameraState
* @private
*/
_getCameraState() {
this.cameraState.previousWidth = this.frame.canvas.width;
_getCameraState(pixelRatio = this.pixelRatio) {
this.cameraState.previousWidth = this.frame.canvas.width / pixelRatio;
this.cameraState.scale = this.body.view.scale;
this.cameraState.position = this.DOMtoCanvas({x: 0.5 * this.frame.canvas.width, y: 0.5 * this.frame.canvas.height});
this.cameraState.position = this.DOMtoCanvas({x: 0.5 * this.frame.canvas.width / pixelRatio, y: 0.5 * this.frame.canvas.height / pixelRatio});
}
/**
@ -99,16 +99,17 @@ class Canvas {
*/
_setCameraState() {
if (this.cameraState.scale !== undefined) {
this.body.view.scale = this.body.view.scale * (this.frame.canvas.clientWidth / this.cameraState.previousWidth);
this.body.view.scale = this.cameraState.scale * ((this.frame.canvas.width / this.pixelRatio) / this.cameraState.previousWidth);
// this comes from the view module.
var viewCenter = this.DOMtoCanvas({
var currentViewCenter = this.DOMtoCanvas({
x: 0.5 * this.frame.canvas.clientWidth,
y: 0.5 * this.frame.canvas.clientHeight
});
var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node
x: viewCenter.x - this.cameraState.position.x,
y: viewCenter.y - this.cameraState.position.y
x: currentViewCenter.x - this.cameraState.position.x,
y: currentViewCenter.y - this.cameraState.position.y
};
this.body.view.translation.x += distanceFromCenter.x * this.body.view.scale;
this.body.view.translation.y += distanceFromCenter.y * this.body.view.scale;
@ -127,7 +128,7 @@ class Canvas {
return value + 'px';
}
}
throw new Error('Could not use the value supplie for width or height:' + value);
throw new Error('Could not use the value supplied for width or height:' + value);
}
@ -227,7 +228,6 @@ class Canvas {
* or '30%')
*/
setSize(width = this.options.width, height = this.options.height) {
this._getCameraState();
width = this._prepareValue(width);
height= this._prepareValue(height);
@ -235,7 +235,18 @@ class Canvas {
let oldWidth = this.frame.canvas.width;
let oldHeight = this.frame.canvas.height;
// update the pixelratio
let ctx = this.frame.canvas.getContext("2d");
let previousRation = this.pixelRatio; // we cache this because the camera state storage needs the old value
this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio || 1);
if (width != this.options.width || height != this.options.height || this.frame.style.width != width || this.frame.style.height != height) {
this._getCameraState(previousRation);
this.frame.style.width = width;
this.frame.style.height = height;
@ -254,6 +265,11 @@ class Canvas {
// this would adapt the width of the canvas to the width from 100% if and only if
// there is a change.
// store the camera if there is a change in size.
if (this.frame.canvas.width != Math.round(this.frame.canvas.clientWidth * this.pixelRatio) || this.frame.canvas.height != Math.round(this.frame.canvas.clientHeight * this.pixelRatio)) {
this._getCameraState(previousRation);
}
if (this.frame.canvas.width != Math.round(this.frame.canvas.clientWidth * this.pixelRatio)) {
this.frame.canvas.width = Math.round(this.frame.canvas.clientWidth * this.pixelRatio);
emitEvent = true;
@ -271,8 +287,11 @@ class Canvas {
oldWidth: Math.round(oldWidth / this.pixelRatio),
oldHeight: Math.round(oldHeight / this.pixelRatio)
});
// restore the camera on change.
this._setCameraState();
}
this._setCameraState();
return emitEvent;
};

+ 5
- 7
lib/network/modules/CanvasRenderer.js View File

@ -147,13 +147,11 @@ class CanvasRenderer {
this.canvas.setSize();
}
if (this.pixelRatio === undefined) {
this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio || 1);
}
this.pixelRatio = (window.devicePixelRatio || 1) / (ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio || 1);
ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0);

+ 24
- 0
lib/network/modules/LayoutEngine.js View File

@ -1,6 +1,7 @@
'use strict'
let util = require('../../util');
import NetworkUtil from '../NetworkUtil';
class LayoutEngine {
constructor(body) {
@ -207,6 +208,7 @@ class LayoutEngine {
let after = this.body.nodeIndices.length;
if ((before == after && levels % 3 !== 0) || levels > MAX_LEVELS) {
this._declusterAll();
this.body.emitter.emit("_layoutFailed");
console.info("This network could not be positioned by this version of the improved layout algorithm.");
return;
}
@ -218,6 +220,15 @@ class LayoutEngine {
// position the system for these nodes and edges
this.body.modules.kamadaKawai.solve(this.body.nodeIndices, this.body.edgeIndices, true);
// shift to center point
this._shiftToCenter();
// perturb the nodes a little bit to force the physics to kick in
for (let i = 0; i < this.body.nodeIndices.length; i++) {
this.body.nodes[this.body.nodeIndices[i]].x += (0.5 - this.seededRandom())*50;
this.body.nodes[this.body.nodeIndices[i]].y += (0.5 - this.seededRandom())*50;
}
// uncluster all clusters
this._declusterAll();
@ -227,6 +238,19 @@ class LayoutEngine {
}
}
/**
* Move all the nodes towards to the center so gravitational pull wil not move the nodes away from view
* @private
*/
_shiftToCenter() {
let range = NetworkUtil._getRangeCore(this.body.nodes, this.body.nodeIndices);
let center = NetworkUtil._findCenter(range);
for (let i = 0; i < this.body.nodeIndices.length; i++) {
this.body.nodes[this.body.nodeIndices[i]].x -= center.x;
this.body.nodes[this.body.nodeIndices[i]].y -= center.y;
}
}
_declusterAll() {
let clustersPresent = true;
while (clustersPresent === true) {

+ 3
- 1
lib/network/modules/PhysicsEngine.js View File

@ -86,12 +86,14 @@ class PhysicsEngine {
};
util.extend(this.options, this.defaultOptions);
this.timestep = 0.5;
this.layoutFailed = false;
this.bindEventListeners();
}
bindEventListeners() {
this.body.emitter.on('initPhysics', () => {this.initPhysics();});
this.body.emitter.on('_layoutFailed', () => {this.layoutFailed = true;});
this.body.emitter.on('resetPhysics', () => {this.stopSimulation(); this.ready = false;});
this.body.emitter.on('disablePhysics', () => {this.physicsEnabled = false; this.stopSimulation();});
this.body.emitter.on('restorePhysics', () => {
@ -191,7 +193,7 @@ class PhysicsEngine {
else {
this.stabilized = false;
this.ready = true;
this.body.emitter.emit('fit', {}, false);
this.body.emitter.emit('fit', {}, this.layoutFailed); // if the layout failed, we use the approximation for the zoom
this.startSimulation();
}
}

+ 31
- 84
lib/network/modules/View.js View File

@ -1,4 +1,6 @@
var util = require('../../util');
let util = require('../../util');
import NetworkUtil from '../NetworkUtil';
class View {
constructor(body, canvas) {
@ -29,80 +31,25 @@ class View {
}
/**
* Find the center position of the network
* @private
*/
_getRange(specificNodes = []) {
var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node;
if (specificNodes.length > 0) {
for (var i = 0; i < specificNodes.length; i++) {
node = this.body.nodes[specificNodes[i]];
if (minX > (node.shape.boundingBox.left)) {
minX = node.shape.boundingBox.left;
}
if (maxX < (node.shape.boundingBox.right)) {
maxX = node.shape.boundingBox.right;
}
if (minY > (node.shape.boundingBox.top)) {
minY = node.shape.boundingBox.top;
} // top is negative, bottom is positive
if (maxY < (node.shape.boundingBox.bottom)) {
maxY = node.shape.boundingBox.bottom;
} // top is negative, bottom is positive
}
}
else {
for (var i = 0; i < this.body.nodeIndices.length; i++) {
node = this.body.nodes[this.body.nodeIndices[i]];
if (minX > (node.shape.boundingBox.left)) {
minX = node.shape.boundingBox.left;
}
if (maxX < (node.shape.boundingBox.right)) {
maxX = node.shape.boundingBox.right;
}
if (minY > (node.shape.boundingBox.top)) {
minY = node.shape.boundingBox.top;
} // top is negative, bottom is positive
if (maxY < (node.shape.boundingBox.bottom)) {
maxY = node.shape.boundingBox.bottom;
} // top is negative, bottom is positive
}
}
if (minX === 1e9 && maxX === -1e9 && minY === 1e9 && maxY === -1e9) {
minY = 0, maxY = 0, minX = 0, maxX = 0;
}
return {minX: minX, maxX: maxX, minY: minY, maxY: maxY};
}
/**
* @param {object} range = {minX: minX, maxX: maxX, minY: minY, maxY: maxY};
* @returns {{x: number, y: number}}
* @private
*/
_findCenter(range) {
return {x: (0.5 * (range.maxX + range.minX)),
y: (0.5 * (range.maxY + range.minY))};
}
/**
* This function zooms out to fit all data on screen based on amount of nodes
* @param {Object} Options
* @param {Boolean} [initialZoom] | zoom based on fitted formula or range, true = fitted, default = false;
*/
fit(options = {nodes:[]}, initialZoom = false) {
var range;
var zoomLevel;
let range;
let zoomLevel;
if (options.nodes === undefined || options.nodes.length === 0) {
options.nodes = this.body.nodeIndices;
}
if (initialZoom === true) {
// check if more than half of the nodes have a predefined position. If so, we use the range, not the approximation.
var positionDefined = 0;
for (var nodeId in this.body.nodes) {
let positionDefined = 0;
for (let nodeId in this.body.nodes) {
if (this.body.nodes.hasOwnProperty(nodeId)) {
var node = this.body.nodes[nodeId];
let node = this.body.nodes[nodeId];
if (node.predefinedPosition === true) {
positionDefined += 1;
}
@ -113,24 +60,24 @@ class View {
return;
}
range = this._getRange(options.nodes);
range = NetworkUtil._getRange(this.body.nodes, options.nodes);
var numberOfNodes = this.body.nodeIndices.length;
let numberOfNodes = this.body.nodeIndices.length;
zoomLevel = 12.662 / (numberOfNodes + 7.4147) + 0.0964822; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
// correct for larger canvasses.
var factor = Math.min(this.canvas.frame.canvas.clientWidth / 600, this.canvas.frame.canvas.clientHeight / 600);
let factor = Math.min(this.canvas.frame.canvas.clientWidth / 600, this.canvas.frame.canvas.clientHeight / 600);
zoomLevel *= factor;
}
else {
this.body.emitter.emit("_resizeNodes");
range = this._getRange(options.nodes);
range = NetworkUtil._getRange(this.body.nodes, options.nodes);
var xDistance = Math.abs(range.maxX - range.minX) * 1.1;
var yDistance = Math.abs(range.maxY - range.minY) * 1.1;
let xDistance = Math.abs(range.maxX - range.minX) * 1.1;
let yDistance = Math.abs(range.maxY - range.minY) * 1.1;
var xZoomLevel = this.canvas.frame.canvas.clientWidth / xDistance;
var yZoomLevel = this.canvas.frame.canvas.clientHeight / yDistance;
let xZoomLevel = this.canvas.frame.canvas.clientWidth / xDistance;
let yZoomLevel = this.canvas.frame.canvas.clientHeight / yDistance;
zoomLevel = (xZoomLevel <= yZoomLevel) ? xZoomLevel : yZoomLevel;
}
@ -142,8 +89,8 @@ class View {
zoomLevel = 1.0;
}
var center = this._findCenter(range);
var animationOptions = {position: center, scale: zoomLevel, animation: options.animation};
let center = NetworkUtil._findCenter(range);
let animationOptions = {position: center, scale: zoomLevel, animation: options.animation};
this.moveTo(animationOptions);
}
@ -157,7 +104,7 @@ class View {
*/
focus(nodeId, options = {}) {
if (this.body.nodes[nodeId] !== undefined) {
var nodePosition = {x: this.body.nodes[nodeId].x, y: this.body.nodes[nodeId].y};
let nodePosition = {x: this.body.nodes[nodeId].x, y: this.body.nodes[nodeId].y};
options.position = nodePosition;
options.lockedOnNode = nodeId;
@ -229,9 +176,9 @@ class View {
// set the scale so the viewCenter is based on the correct zoom level. This is overridden in the transitionRedraw
// but at least then we'll have the target transition
this.body.view.scale = this.targetScale;
var viewCenter = this.canvas.DOMtoCanvas({x: 0.5 * this.canvas.frame.canvas.clientWidth, y: 0.5 * this.canvas.frame.canvas.clientHeight});
let viewCenter = this.canvas.DOMtoCanvas({x: 0.5 * this.canvas.frame.canvas.clientWidth, y: 0.5 * this.canvas.frame.canvas.clientHeight});
var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node
let distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node
x: viewCenter.x - options.position.x,
y: viewCenter.y - options.position.y
};
@ -268,14 +215,14 @@ class View {
* @private
*/
_lockedRedraw() {
var nodePosition = {x: this.body.nodes[this.lockedOnNodeId].x, y: this.body.nodes[this.lockedOnNodeId].y};
var viewCenter = this.canvas.DOMtoCanvas({x: 0.5 * this.canvas.frame.canvas.clientWidth, y: 0.5 * this.canvas.frame.canvas.clientHeight});
var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node
let nodePosition = {x: this.body.nodes[this.lockedOnNodeId].x, y: this.body.nodes[this.lockedOnNodeId].y};
let viewCenter = this.canvas.DOMtoCanvas({x: 0.5 * this.canvas.frame.canvas.clientWidth, y: 0.5 * this.canvas.frame.canvas.clientHeight});
let distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node
x: viewCenter.x - nodePosition.x,
y: viewCenter.y - nodePosition.y
};
var sourceTranslation = this.body.view.translation;
var targetTranslation = {
let sourceTranslation = this.body.view.translation;
let targetTranslation = {
x: sourceTranslation.x + distanceFromCenter.x * this.body.view.scale + this.lockedOnNodeOffset.x,
y: sourceTranslation.y + distanceFromCenter.y * this.body.view.scale + this.lockedOnNodeOffset.y
};
@ -300,7 +247,7 @@ class View {
this.easingTime += this.animationSpeed;
this.easingTime = finished === true ? 1.0 : this.easingTime;
var progress = util.easingFunctions[this.animationEasingFunction](this.easingTime);
let progress = util.easingFunctions[this.animationEasingFunction](this.easingTime);
this.body.view.scale = this.sourceScale + (this.targetScale - this.sourceScale) * progress;
this.body.view.translation = {

+ 81
- 60
test/networkTest.html View File

@ -1,74 +1,95 @@
<!doctype html>
<html>
<head>
<title>Network | Basic usage</title>
<script type="text/javascript" src="../dist/vis.js"></script>
<link href="../dist/vis.css" rel="stylesheet" type="text/css" />
<title>Vis Popup Example</title>
<style type="text/css">
body {
font: 10pt sans;
}
#mynetwork {
width: 600px;
height: 400px;
height: 600px;
border: 1px solid lightgray;
}
</style>
</head>
<body>
<p>
Create a simple network with some nodes and edges.
</p>
<div id="mynetwork"></div>
<script type="text/javascript">
var nodes = new vis.DataSet()
nodes.add({id:'A', label:"A", x:0, y:0})
nodes.add({id:'B', label:"B", x:100, y:0})
nodes.add({id:'C', label:"C", x:200, y:0})
nodes.add({id:'D', label:"D", x:0, y:100})
nodes.add({id:'E', label:"E", x:100, y:100})
nodes.add({id:'F', label:"F", x:200, y:100})
nodes.add({id:'G', label:"G", x:-100, y:200})
var edges = new vis.DataSet()
edges.add({id:'1', from:"A", to:"B"})
edges.add({id:'2', from:"B", to:"C"})
edges.add({id:'3', from:"D", to:"E"})
edges.add({id:'4', from:"E", to:"F"})
edges.add({id:'5', from:"D", to:"G"})
var options = {physics:false, edges:{smooth:false}};
var network = new vis.Network(document.getElementById("mynetwork"), {
nodes: nodes,
edges: edges
}, options)
console.log("MAKE C1")
network.cluster({
joinCondition:function(nodeOptions) {
return nodeOptions.id == "B" || nodeOptions.id == "E";
},
clusterNodeProperties: {id:'C1', label:"C1"}
})
console.log("MAKE C2")
network.cluster({
joinCondition:function(nodeOptions) {
return nodeOptions.id == "D" || nodeOptions.id == "G";
},
clusterNodeProperties: {id:'C2', label:"C2"}
})
network.openCluster('C1', {
releaseFunction: function (cpos, npos) {
console.log(npos)
return npos;
<script src="http://visjs.org/dist/vis.js" type="text/javascript" ></script>
<link href="http://visjs.org/dist/vis.css" rel="stylesheet" type="text/css"/>
<!--<link href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet" type="text/css" />-->
<!--<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>-->
<!--<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js" type="text/javascript"></script>-->
<script type="text/javascript">
var network = null;
var network2 = null;
function draw() {
var nodes = [];
var edges = [];
nodes.push({ id: 1, label: 'Node1' });
nodes.push({ id: 2, label: 'Node2' });
edges.push({ from: 2, to: 1 });
var data = {
nodes: nodes,
edges: edges
};
// create a network
var container = document.getElementById('mynetwork');
var options = {
layout: {
hierarchical: {
direction: "UD"
}
}
};
network = new vis.Network(container, data, options);
// $('#divTreemapPopUp').modal('show');
var nodes2 = [];
var edges2 = [];
nodes2.push({ id: 1, label: 'Node1' });
nodes2.push({ id: 2, label: 'Node2' });
edges2.push({ from: 2, to: 1 });
var data2 = {
nodes: nodes2,
edges: edges2
};
// create a network
var container2 = document.getElementById('mynetwork2');
var options2 = {
layout: {
hierarchical: {
direction: "UD"
}
}
};
network2 = new vis.Network(container2, data2, options2);
}
})
</script>
</head>
<body onload="draw();">
<h2>Popup Example</h2>
<div style='display: block;' class='modal fade' id='divTreemapPopUp' tabindex='-1' role='dialog' aria-labelledby='myModalLabel' aria-hidden='true'>
<div class='modal-dialog'><div class='modal-content'><div class='modal-header'>
<button type='button' class='close' data-dismiss='modal' aria-hidden='true'>×</button>
<h4 class='modal-title'>TreeMap</h4></div>
<div class='modal-body'>
<div id='mynetwork'></div></div>
<div class='modal-footer'><button type='button' class='btn btn-default' data-dismiss='modal'>Close</button></div></div></div></div>
</script>
<div id='mynetwork2'></div>
</body>
</html>
</html>

Loading…
Cancel
Save