Adding elements in bulk on JointJS leads to performance issues - javascript

We are trying to build a Visualization Framework using JointJS (we are not using Rappid). SVG is used to render graphical data on the browser.
We have a scenario where we have a model with approximately 4500 ports.
We have used ports as elements and NOT as attributes. Meaning to say that we have embedded ports as cells inside a model. There is a certain reason why need such a setup.
When such a model is added onto the graph, it takes roughly 8 to 9 seconds to render. Once the model is rendered, moving and repositioning the model works okay. However, when we try to add further models the browser tends to crash.
This is how we have created the Paper object
function createPaper(options) {
var paper = new joint.dia.Paper({
el: options.el,
width: options.width,
height: options.height,
model: options.graph,
gridSize: 1,
label:options.name,
async : { batchSize: 250}
});
return paper;
}
This is how we have created the model
function createModel(options) {
var model = new joint.shapes.devs.Model({
position:options.position,
size:options.size,
attrs: options.attrs
});
return model;
}
This is how we have created the port as a custom model
joint.shapes.custom = {};
joint.shapes.custom.Port = joint.shapes.basic.Generic.extend(_.extend({}, joint.shapes.basic.PortsModelInterface, {
markup: '<g class="rotatable"><g class="scalable"><path class="port-body" d = "M 0 0 L 0 0 L 15 5 L 0 10 L 0 10 z"></path></g><text class="port-label"/></g>',
defaults: joint.util.deepSupplement({
type: 'devs.Port',
size: { width: 10, height: 10 },
position : {x : 0, y : 0},
attrs: {
'.': { magnet: false },
'.body': {
width: 150, height: 250,
stroke: '#000000'
},
'.port-body': {
r: 5,
magnet: true,
stroke: '#000000',
fill : 'yellow'
},
text: {
'pointer-events': 'none'
},
}
}, joint.shapes.basic.Generic.prototype.defaults),
}));
joint.shapes.custom.PortView = joint.dia.ElementView.extend(joint.shapes.basic.PortsViewInterface);
function createPort(options){
var port=new joint.shapes.custom.Port({
position:options.position,
size:options.size,
attrs: options.attrs
});
return port;
}
Below is the way we are adding models and ports to the graph
var cells = [];
var model = createModel(options);
//Add the cell to the graph
cells.push(model);
var totalInports=options.inPorts.length;
var totalOutports=options.outPorts.length;
//Add the inports
for (var portIndex = 0; portIndex < totalInports; portIndex++) {
port = createPort(options.inPorts[portIndex]);
model.embed(port);
cells.push(port);
}
//Add the outports
for (var portIndex = 0; portIndex < totalOutports; portIndex++) {
port = createPort(options.outPorts[portIndex]);
model.embed(port);
cells.push(port);
}
graph.addCells(cells);
Could anybody kindly give some answers to the below questions -
Are we using the paper and models in the right way ?
Are there better ways of adding cells to the graph which could improve the
performance ?
Thanks in Advance

I know that this is an old question that has been left unanswered for a long time. My take is to use:
graph.fromJSON or graph.addCells(/*Array of json cells*/)
Then, try to translate all the operations as a compound json that will be passed to graph.fromJSON or graph.addCells.

Related

filtering nodes by degree in cytoscape.js

I have a cytoscape.js graph and i want to filter nodes by degree size. for example if node1 is only connected to node2 and node3 , when I select threshold 3 from filter, all nodes witch have less than 3 edges will be removed. now I have this problems:
1) how can I filter by degree? I know var collection = cy.elements("node[weight > 2]"); select a collection and cy.remove(collection); will delete those nodes but how remove by number of connected edges?
2)after removing some nodes, its needed to rerun layout for better representation. I found a solution but it takes time roughly as big as before removing nodes. I tested this code :
code for adding node:
for (var i = 0; i < keys.length; i++) {
try {
cy.add({
data: {
id: keys[i],
weight: i
}
}
);
}
code for layout:
var layout;
cy.ready(function () {
layout = cy.layout({
name: 'cose-bilkent',
animate: false,
padding: 100,
nodeRepulsion: 20000,
idealEdgeLength: 700,
edgeElasticity: 10,
numIter: 2500,
initialEnergyOnIncremental: 0.01,
ready: function () {
}, // on layoutready
stop: function () {
cy.endBatch();
var b64key = 'base64,';
var b64 = cy.png().substring(cy.png().indexOf(b64key) + b64key.length);
var imgBlob = b64toBlob(b64, 'image/png');
saveAs(imgBlob, 'graph.png');
alert(new Date().getTime() - n);
n = new Date().getTime();
}
});
layout.run();
});
and on button click I run this :
var collection = cy.elements("node[weight > 2]");
cy.remove( collection );
layout.run();
but it takes 37sec for loading this graph with 3 nodes!!!

FabricJS getCircle by Id or Name

I am creating multiple circle objects, each with a unique name (or id).
When I want to select an object, I have to search through all circle objects and return the right object. With many objects this is not good for the performance I guess. Is there a way to select an object by a unique attribute?
I created a simple JSFiddle to show my current workaround to get a circle Object:
https://jsfiddle.net/t47vvtec/5/
I compare every object with the attribute and if one matches it, the object will be returned. Maybe you can help me to find an easier solution.
Here is my code to get an object so far:
function getPoint(name)
{
var line_point_array = canvas.getObjects('circle');
for (var i = 0; i < line_point_array.length; i++) {
var point = line_point_array[i];
if (point.name == name) {
return point;
}
}
}
Thanks :)
You could try something like (https://jsfiddle.net/cssimsek/akbe97c5/1/):
var FabricCanvasObject = function(canvasId, attrSet) {
this.theCanvas = new fabric.Canvas(canvasId, attrSet);
this.addShape = function(shapeAttrSet) {
var newShape = this.theCanvas.add(new fabric.Circle(shapeAttrSet));
this.canvasElements.length += 1;
this.canvasElements[shapeAttrSet.name + this.canvasElements.length] = newShape;
};
this.canvasElements = { length: 0 };
};
var myFabric = new FabricCanvasObject('c', {
targetFindTolerance: 15
});
console.log(myFabric);
myFabric.addShape({
radius: 20,
fill: 'green',
left: 100,
top: 100,
name: 'circle',
id: 1
});
myFabric.addShape({
radius: 20,
fill: 'red',
left: 150,
top: 150,
name: 'circle',
id: 2
});
myFabric.addShape({
radius: 20,
fill: 'blue',
left: 200,
top: 200,
name: 'circle',
id: 3
});
console.log(myFabric.canvasElements);
console.log(myFabric.canvasElements.circle1);

SVG and mouseover

I'm a JS newbie and have converted an SVG to JS using Raphael. I'm trying to make an interactive map of the USA with mouseover effects over multiple paths and circles.
I have the states as variables with a parent variable and the cities I've visted as variables with a parent variable. Here's a snippet of my JS:
var states = rsr.set();
var connecticut = rsr.path("M877.198,184.1l-0.6-4.2l-0.8-4.4l-1.602-6L870,170.4l-21.802,4.8l0.602,3.3l1.5,7.3v8.102 l-1.102,2.3l1.802,2.101l5-3.399l3.6-3.2l1.9-2.1l0.8,0.6l2.7-1.5l5.198-1.1L877.198,184.1z").attr({fill: '#D3D3D3','stroke-width': '0','stroke-opacity': '1'});
connecticut.node.id = 'Connecticut';
states.push(connecticut);
var cities = rsr.set();
var losAngeles = rsr.circle(87, 349, 5).attr({fill: '#3F3F3F','stroke-width': '0','stroke-opacity': '1'});
cities.push(losAngeles);
I'm having trouble creating mouseover effects on both the states AND cities. I'm thinking it could have something to do with the z-index?
I've written these for loops so far but only one ever works at a time.
for (var i = 0; i <= states.length; i++) {
states[i].mouseover(function() {
this.animate({
fill: '#fff',
transform: 's1.05'
}, 200);
});
states[i].mouseout(function() {
this.animate({
fill: '#D3D3D3',
transform: 's1'
}, 200);
});
}
for (var i = 0; i <= cities.length; i++) {
cities[i].mouseover(function() {
this.animate({
r : 10,
}, 200);
});
cities[i].mouseout(function() {
this.animate({
r : 5,
}, 200);
});
}
I've tried using toFront(); and toBack(); and still can't get it to work. Any suggestions?

Markerclusterer set marker cluster icon depending on markers inside it

Let's say that I want to make a webpage that will show approximate number of free parking spots left on each parking(due to confidentiality I'm not allowed to give any details of what I'm actually doing). For this I'm using Google maps and Markerclusterer. So for parking with less than 5% spots left I'd like to show a red marker, for parkings with 5%-25% spots I'd show a yellow one and for ones with more than 25% free spots I want to make it green. So far I could make those markers and cluster them, but here's the tricky part(and question itself):
How can I make a cluster icon dependant on the markers inside it?
For example:
Parking A is green
Parking B is red
Parking C is green
Parking D is yellow
When zoomed out I want to show the cluster that has all 4 of them red(worst of all). When zoomed in I'd get 2 clusters(A+B and C+D). I want the first cluster(A+B) to be red and second(C+D) should be yellow.
What I did so far:
var mcOptions = {gridSize: 50, maxZoom: 15, styles: [{
height: 46,
url: "///Users/Novarg/Downloads/foundation-5.4.7/img/greenC.png",
width: 46
},
{
height: 46,
url: "///Users/Novarg/Downloads/foundation-5.4.7/img/redC.png",
width: 46
}]};
var markers = [];
for (var i = 0; i < 100; i++) {
var latLng = new google.maps.LatLng(51 + Math.random(),
4 + Math.random());
var marker = new google.maps.Marker({'position': latLng, icon: 'img/greenP.png'});
markers.push(marker);
}
for (var i = 0; i < 20; i++) {
var latLng = new google.maps.LatLng(51 - Math.random(),
4 - Math.random());
var marker = new google.maps.Marker({'position': latLng, icon: 'img/redP.png'});
markers.push(marker);
}
var markerCluster = new MarkerClusterer(map, markers, mcOptions);
}
google.maps.event.addDomListener(window, 'load', initialize);
Right now I only have red and green markers, which should be enough to test it. But behaviour of this cluster now is as follows:
All clusters with less than 10 markers in it are green
All clusters with more than 9 markers in it are red
EDIT
From this link I found that what I might need is Calculator. So I tried it, but still no luck(although I think that I'm getting closer. Actually, I hope I'm very close to the solution right now).
So I tried to change my options:
var mcOptions = {gridSize: 50, maxZoom: 15, styles: [{
height: 46,
url: "///Users/Novarg/Downloads/foundation-5.4.7/img/greenC.png",
width: 46
},
{
height: 46,
url: "///Users/Novarg/Downloads/foundation-5.4.7/img/redC.png",
width: 46
}],
calculator: function(markers, numStyles) {
for (var i = 0; i < markers.length; i++) {
if (markers[i].getIcon().indexOf("redP.png") > -1) {
return {text: markers.length, index: 1};
}
}
return {text: markers.length, index: 0};
}
};
But the Calculator is never being used. I tested it by putting a simple alert('test'); inside it.
I hope this additional info will help you to help me find a solution.
As I already mentioned in the edit, I was very close to the solution. So I took another(fresh) look at the code today, checked the docs once again and noticed the following in the ClusterIconInfo:
index number The index plus 1 of the element in the styles array to be used to style the cluster icon.
So basically I solved this problem simply by incrementing the index by one(and I also moved Calculator to be a var and then used setCalculator() method on the MarkerClusterer itself). So my code became:
var calc = function(markers, numStyles) {
for (var i = 0; i < markers.length; i++) {
if (markers[i].getIcon().indexOf("redP") > -1) {
return {text: markers.length, index: 2};
}
}
return {text: markers.length, index: 1};
}
var mcOptions = {gridSize: 50, maxZoom: 15, styles: [{
height: 46,
url: "img/greenC.png",
width: 46
},
{
height: 46,
url: "img/redC.png",
width: 46
}]
};
var markerCluster = new MarkerClusterer(map, markers, mcOptions);
markerCluster.setCalculator(calc);
And now it works like a charm(as it should).
Hopefully this could help somebody someday.
Extending on Novarg's answer, you will need to define a Calculator, which selects a style from the initial styles array passed into the MarkerClusterer's constructor.
If you want to extend the original functionality (which controls the style based on the number of markers the Cluster contains, you can add styles in multiples of 3 in the initial styles array and call the original Calculator in your calculator.
// create MarkerClusterer instance with array of styles, multiples of 3
const markerClusterer = new MarkerClusterer(this.map, this.kioskMarkers, {
clusterClass: "custom-clustericon",
styles: [
{
width: 30,
height: 30,
className: "normal"
},
{
width: 40,
height: 40,
className: "normal"
},
{
width: 50,
height: 50,
className: "normal"
},
{
width: 30,
height: 30,
className: "special"
},
{
width: 40,
height: 40,
className: "special"
},
{
width: 50,
height: 50,
className: "special"
}]
});
// your function that determines special styling
const checkForYourSpecialCondition = () => true;
// new calculator function
const newCalculator = (markers, numStyles) => {
const offset = checkForYourSpecialCondition() ? 3 : 0;
const { text, index } = MarkerCluster.CALCULATOR(markers, numStyles);
return {text, index: index + offset};
}
markerClusterer.setCalculator(newCalculator);

JQuery Gridster with HTML/Javascript Windows 8 Store App

I decided to create my first windows store app with html/javascript and since you cannot do drag and drop with javascript and listview i have looked for alternatives. Windows store apps support jquery so I found Gridster. It is a jquery plugin to display stuff in a grid with draggable reordering. Each item can be resized as well. I want to use this in my windows store app. Here is a fiddle of the code i inputted into blend, into the html page
var layout;
var grid_size = 100;
var grid_margin = 5;
var block_params = {
max_width: 6,
max_height: 6
};
$(function() {
layout = $('.layouts_grid ul').gridster({
widget_margins: [grid_margin, grid_margin],
widget_base_dimensions: [grid_size, grid_size],
serialize_params: function($w, wgd) {
return {
x: wgd.col,
y: wgd.row,
width: wgd.size_x,
height: wgd.size_y,
id: $($w).attr('data-id'),
name: $($w).find('.block_name').html(),
};
},
min_rows: block_params.max_height
}).data('gridster');
$('.layout_block').resizable({
grid: [grid_size + (grid_margin * 2), grid_size + (grid_margin * 2)],
animate: false,
minWidth: grid_size,
minHeight: grid_size,
containment: '#layouts_grid ul',
autoHide: true,
stop: function(event, ui) {
var resized = $(this);
setTimeout(function() {
resizeBlock(resized);
}, 300);
}
});
$('.ui-resizable-handle').hover(function() {
layout.disable();
}, function() {
layout.enable();
});
function resizeBlock(elmObj) {
var elmObj = $(elmObj);
var w = elmObj.width() - grid_size;
var h = elmObj.height() - grid_size;
for (var grid_w = 1; w > 0; w -= (grid_size + (grid_margin * 2))) {
grid_w++;
}
for (var grid_h = 1; h > 0; h -= (grid_size + (grid_margin * 2))) {
grid_h++;
}
layout.resize_widget(elmObj, grid_w, grid_h);
}
});​
The js references and css can be seen in the source here (where i got the code)
http://jsfiddle.net/maxgalbu/UfyjW/show
I got it working and can drag the boxes around and reorder and resize them...my issue now is how to get it to expand horizontal instead of vertically..the more stuff i put into it, they all go to the bottom. I would like it to expand to the right as new items get added (like the start menu)...it would be perfect if i could turn the grid counter clockwise and keep the items the same orientation..but i know it may not be that easy
i have tried changing some values in the script (in the above code sample) in the html and got some more columns for the boxes to snap to..but eventually they all just revert to snapping to the bottom of one of the first columns...do i have to edit the referenced js files? There's like 4 of them, which one?

Categories

Resources