I am making a web app to show relationships between items using Vis.js, everything works perfectly fine until I get to the point where I need to display ~260 nodes with ~1200 edges between them.
Once I get to that amount of nodes, the graph just shows a blank space and a blue line, nothing else. As soon as I try to zoom it, the line disappears and it's all white.
When I look at the position of the nodes I can see that many of them are in negative or very big x, y positions (generally -300 for x and around 478759527705558300000 for y).
I have tried, to no avail, to disable physics. The graph is in hierarchichal mode, with levels manually set in the code, but the levels are correct.
Network options (the improvedLayout option was just a possibility I found on the internet; it works just the same if I remove it):
var options = {
layout: {
improvedLayout: false,
hierarchical: {
direction: direction,
sortMethod: "directed"
}
}
}
Screenshot:
I have hierarchical layout graph which consists of around 615 nodes and 614 edges (excluding 40 odd cluster nodes, some of them cluster of clusters). I landed into same problem with visjs.
One quick thing which helped me to get over this problem was to explicitly call network.stabilize() method with an argument specifying number of iterations. Default iterations are 1000. I passed 10000 and graph stabilized it self nicely. It took a few more seconds, i was fine with that. But stabilization times shoots up as number nodes increased to ~1000. So i started looking into visjs code for solution.
While looking in visjs code, i found that inside function setupHierarchicalLayout() there is a call to _condenseHierarchy().
This method tries to minimize white spaces between nodes and edges (yet to understand the code fully). _condenseHierarchy() modifies coordinates of a node. See Y coordinate before and after call to _condenseHierarchy() below:
(this.body.nodes["node-11"]).y
1530
(this.body.nodes["node-11"]).y
64920
When a node gets a distant position it takes lots of iterations (in stabilize) to bring it closer together with other nodes in graph. I disabled _condenseHierarchy() and got the graph displayed nicely.
I'm sure disabling _condenseHierarchy() would bring in some other issues as i proceed further. I am going to spend some more time to understand and experiment with _condenseHierarchy().
To solve this problem, you can adjust hierarchical layout parameters, such as nodeSpacing, levelSeparation and treeSpacing. Here is an example hierarchicalLayoutWithoutPhysics
Related
I'm working with vis.js to display some graphs. The problem is that layouts with multiple central nodes get noisy (the central nodes' neighbours overlap). A layout similar to the attached image is desirable.
Is it possible to achieve this using vis.js?
Looks like there is an option that should solve your issue (I've found it after playing with physicsConfiguration like I suggested earlier):
physics has avoidOverlap property (float between 0 and 1) that can be used like this:
var options = {
...
physics: {
barnesHut: {
avoidOverlap: 0.5
},
...
}
}
If you try it in the configuration demo, you'll see that with avoidOverlap equal to 0 it's quite possible to drag and move nodes so that those overlap edges and the network stays in that position:
but once we increase this value, nodes start to be bounced from edges and can't really stay in that position:
Note though, that this option doesn't prevent edge-edge crossing, only edge-node overlapping (see physics/barnesHut/avoidOverlap). The physics docs page doesn't contain the word "cross" at all and each occurance of "overlap" is about edge-node thing.
So this will make the layout less noisy, but won't eliminate crossed edges.
I have a mathematical problem that I am trying to solve and want to realize in javascript.
I have a "space" meaning: a rectangular format. This shall work as a kind of container.
Then I have smaller forms: other rectangular ones and circles.
I need to find out a solution how to calculate how many of these forms fit into the container.
So I kind of need the ideal way, how to stack these items in there.
If it is too many, leaving out some. And if it is too few, stacking them from bottom up.
I am trying to realize this in Javascript but not getting far.
From my vision it should be kind of like this:
var items = [circle1,2],[rect1,2,4],[rect2,5,6]]; // array with the forms to put inside container, stating a radius for circle and width/height for rectangles
var container = [10, 4]; // given the size of the container in width and height.
function stackItIn (container, items){
// now this is where I am totally lost
}
The premium part would be even showing it graphically at the end.
Any help would be appreciated!
Thank you!
You can try a treemap. Sort the tiles and pick the first and create a node in a tree. Split the tree on both axis and pick the next tile and find the best fit of the nodes.
I have implemented two dygraphs with custom option verticalCrosshair : true here:
https://rawgit.com/danielkrizian/dygraphs/master/tests/synchronize-Crosshair.html
When I hover over any of the graphs at a particular x point, I want all graphs to display the vertical crosshair at that point.
So far I've been able to get this working on the top graph (gs[0]) like this:
highlightCallback: function(e, x, pts, row) {
var sel = gs[0].getSelection();
gs[1].setSelection(sel);
},
Nothing happens when I hover over the bottom graph. How to generalize it with for loop over all graphs?
You should put the dygraphs objects in an array and loop over it in your highlightCallback, updating the selection in all the dygraphs other than the one generating the event.
One complication is that highlightCallback doesn't get the dygraph object as a parameter. This is an oversight in the API which I hope to fix in dygraphs 2.0. You can work around it by capturing the relevant Dygraph object in a closure when you set highlightCallback.
See the synchronize demo for some inspiration.
I've solved this with:
highlightCallback: function(e, x, pts, row) {
for (var j = 0; j < gs.length; j++) {
gs[j].setSelection(row);
}
},
See the graphs and source at:
https://rawgit.com/danielkrizian/dygraphs/master/tests/synchronize-Crosshair.html
I have had both of these features up and working for nearly a year. I didn't originate the code, but just did just some editing in an effort to get the labelFollow bit working the way that I liked. I followed the original author(s) and stuffed the code in the library, not in a mere callback in a Dygraph() options argument, but I did not put the verticalCrosshair snippets in a plug-in as I don't know how to write those yet. However, the labelFollow stuff is in the plug-in legend.js, as that's what the original author had done.
As for the code for the verticalCrosshair option, I got it from the apparently esteemable DJCOMXA--- http://www.pixeltradr.com/dygraphs/dygraph.js. Just search for "verticalCrosshair" and you'll find where two tiny snippets have been added to that script.
To complete the verticalCrosshair option it's necessary of course to also add to dygraph-options-reference.js as follows:
// Credit due to DJCOMXA.
"verticalCrosshair": {
"default": "false",
"labels": ["Interactive Elements"],
"type": "boolean",
"description": "Shows vertical line on highlighted point."
},
Now for the other matter of the labelFollow, for proof of prior work on labelFollow it suffices to go to the Google group. You can then click on the link at the very bottom of that page to see a graph that shows both the verticalCrosshair and the labelFollow (the latter being, I believe, the work of "wootwoot" who I more or less copied). Similarly, replace "synchronize-Charts" in that URL for that graph with "customLabel_Crosshair" and you'll be taken to a prettier example (I think that I'm limited as to the number of links that I can provide, hence the URL re-construction instructions).
I can't now figure out how I got the original labelFollow code. Whatever, I found some modifications to legend.js (and of course dygraph-options-reference.js) by wootwoot and edited them.
I must say that I wrote to danvdk at the gmail address that he provided at the top of dygraph.js, to suggest the utility of these changes, and it was bounced as I was not a member of the club.
Regarding taking inspiration from the Dygraphs sychronization example (the "synchronize demo" link that danvk provided), take your inspiration from it cautiously. Right now it's giving me fits.
Try this: upon page load go to any of the four graphs and in the middle at a skinny section do a zoom in (click drag from left to right); then double-click.
What happened? You zoomed in and the traces filled the graph vertically, but for a little bit of padding--- automatic scaling. And then upon zooming out with the double-click everything appeared to be as before. Ahh... but it isn't. Now move to any OTHER of the four graphs and repeat the first step... zoom in at a the same skinny spot (the data happen to be the same for each of these graphs). Notice that the automatic vertical scaling is missing. And that's a permanent condition until you reload the page.
I've created a 'donut' chart originally from this jsfiddle, using raphael.
I have tweaked this script to suit my needs and currently have this being rendered.
My aim is to animate each slice (at the same time); for example make the blue slice grow to 60%; and the red slice shrink to 40%.
I have been able to redraw the slices by removing the existing one and quickly re-rendering a new one with adjusted values (e.g. 51, 49). But the problem here is that it is instant.
My question is,
(a) Can I animate this without the need to redraw the object (and how)?
(b) If not, how I can animate this effect using a redraw logic?
Yes. There is an example of doing this very thing on the Raphael demos page where you got the pie chart. See the Growing Pie demo.
You should separate the code in which you generate the path into a standalone function so you can use it later to return new paths. In order to use animate(), you'll need to define a function on the customAttributes object; it should return (at least) an object with the path property set to your slice's new path.
Since you have labels, you'll probably want to modify the code such that the pie slices expand/shrink relative to their center, so that you don't have to move the labels, too, since the labels are centered on their slice's "axis."
Update
Here's a JSFiddle with a simple example, pretty much the same as Dmitri's Growing Pie demo, except more like your chart. I export a setValue() method to change slice sizes and call it when the page loads. See his blog post about adding customAttributes, too.
In my last paragraph above, I was off the mark a bit. Your chart wasn't the one with labels; I had them mixed up. Also, it would be harder to keep slices centered, so I didn't do that after all. The animate() function sets each segment to its new starting and ending points on the circle, and Raphael figures out the intermediate points. As you can see, you can pass multiple arguments in an array.
this.customAttributes.slice = function(a0, a1) { /*...*/ }
// ...
chart.push(paper.path().attr({slice:[0, Math.PI/2 ]})
Can't see all the fiddle because I'm on iPod however it sounds like you need to have an animate call inside a function that you will need to write
Use the callback parameter that calls the function it sits inside.
Code your recursively called function so it eventually completes when all the work is done.
Each call to the function will happen at the end of every elapssed time interval you specify...
I've built an analytical data visualization engine for Canvas and have been requested to add tooltip-like hover over data elements to display detailed metrics for the data point under the cursor.
For simple bar & Gaant charts, tree graphs and node maps with simple square areas or specific points of interest, I was able to implement this by overlaying absolutely-positioned DIVs with :hover attributes, but there are some more complicated visualizations such as pie charts and a traffic flow rendering which has hundreds of separate areas defined by bezeir curves.
Is is possible to somehow attach an overlay, or trigger an event when the user mouses over a specific closed path?
Each area for which hover needs to be specified is defined as follows:
context.beginPath();
context.moveTo(segmentRight, prevTop);
context.bezierCurveTo(segmentRight, prevTop, segmentLeft, thisTop, segmentLeft, thisTop);
context.lineTo(segmentLeft, thisBottom);
context.bezierCurveTo(segmentLeft, thisBottom, segmentRight, prevBottom, segmentRight, prevBottom);
/*
* ...define additional segments...
*/
// <dream> Ideally I would like to attach to events on each path:
context.setMouseover(function(){/*Show hover content*/});
// </dream>
context.closePath();
Binding to an object like this is almost trivial to implement in Flash or Silverlight, since but the current Canvas implementation has the advantage of directly using our existing Javascript API and integrating with other Ajax elements, we are hoping to avoid putting Flash into the mix.
Any ideas?
You could handle the mousemove event and get the x,y coordinates from the event. Then you'll probably have to iterate over all your paths to test if the point is over the path. I had a similar problem that might have some code you could use.
Looping over things in this way can be slow, especially on IE. One way you could potentially speed it up - and this is a hack, but it would be quite effective - would be to change the color that each path is drawn with so that it is not noticeable by humans but so that each path is drawn in a different color. Have a table to look up colors to paths and just look up the color of the pixel under the mouse.
Shadow Canvas
The best method I have seen elsewhere for mouseover detection is to repeat the part of your drawing that you want to detect onto a hidden, cleared canvas. Then store the ImageData object. You can then check the ImageData array for the pixel of interest and return true if the alpha value is greater than 0.
// slow part
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.fillRect(100,100,canvas.width-100,canvas.height-100);
var pixels = ctx.getImageData(0,0,canvas.width,canvas.height).data;
// fast part
var idx = 4 * (mouse_x + mouse_y * canvas.width) + 3;
if (pixels[idx]) { // alpha > 0
...
}
Advantages
You can detect anything you want since you're just repeating the context methods. This works with PNG alpha, crazy compound shapes, text, etc.
If your image is fairly static, then you only need to do this one time per area of interest.
The "mask" is slow, but looking up the pixel is dirt cheap. So the "fast part" is great for mouseover detection.
Disadvantages
This is a memory hog. Each mask is W*H*4 values. If you have a small canvas area or few areas to mask, it's not that bad. Use chrome's task manager to monitor memory usage.
There is currently a known issue with getImageData in Chrome and Firefox. The results are not garbage collected right away if you nullify the variable, so if you do this too frequently, you will see memory rise rapidly. It does eventually get garbage collected and it shouldn't crash the browser, but it can be taxing on machines with small amounts of RAM.
A Hack to Save Memory
Rather than storing the whole ImageData array, we can just remember which pixels have alpha values. It saves a great deal of memory, but adds a loop to the mask process.
var mask = {};
var len = pixels.length;
for (var i=3;i<len;i+=4) if ( pixels[i] ) mask[i] = 1;
// this works the same way as the other method
var idx = 4 * (mouse_x + mouse_y * canvas.width) + 3;
if (mask[idx]) {
...
}
This could be done using the method ctx.isPointInPath, but it is not implemented in ExCanvas for IE.
But another solution would be to use HTML maps, like I did for this little library : http://phenxdesign.net/projects/phenx-web/graphics/example.htm you can get inspiration from it, but it is still a little buggy.
I needed to do detect mouse clicks for a grid of squares (like cells of an excel spreadsheet). To speed it up, I divided the grid into regions recursively halving until a small number of cells remained, for example for a 100x100 grid, the first 4 regions could be the 50x50 grids comprising the four quadrants.
Then these could be divided into another 4 each (hence giving 16 regions of 25x25 each).
This requires a small number of comparisons and finally the 25x25 grid could be tested for each cell (625 comparisons in this example).
There is a book by Eric Rowell named "HTML5 CANVAS COOKBOOK". In that book there is a chapter named "Interacting with the Canvas: Attaching Event Listeners to Shapes and Regions". mousedown, mouseup, mouseover, mouseout, mousemove, touchstart, touchend and touchmove events can be implemented. I highly suggest you read that.
This can't be done (well, at least not that easily), because objects you draw on the canvas (paths) are not represented as the same objects in the canvas. What I mean is that it is just a simple 2D context and once you drawn something on it, it completely forgets how it was drawn. It is just a set of pixels for it.
In order to watch mouseover and the likes for it, you need some kind of vector graphics canvas, that is SVG or implement your own on top of existing (which is what Sam Hasler suggested)
I would suggest overlaying an image map with proper coordinates set on the areas to match your canvas-drawn items. This way, you get tooltips AND a whole lot of other DOM/Browser functionality for free.