Is there an equivalent to jQuery's slideDown method for SVG elements? Right now the elements just show up rather suddenly. I would like to soften that a little by sliding down.
Edit:
I'm basing my code on the dynamic stacked bar graph here: http://benjchristensen.com/2011/12/16/dynamic-stacked-bar-chart-using-d3-js/
Instead of one graph with many bars, I have many graphs with one bar. I want to make it so that when the graphs appear, it will slowly slide down.
I don't believe that SVG is easily supported by jQuery. Keith Wood has a SVG specific jQuery library that allows you to do some animation though.
A different option would be to use a graphics library that works with SVG. The first that comes to mind is D3.js, which provides some very easy to use methods for manipulating SVG elements. Additionally, the selector style is similar to what you may be used to for jQuery.
If you're using D3, I'd check out the transition methods. There's quite a bit you can do and D3 is incredibly powerful. An example would be:
var rect = d3.select('#rectangle');
// this selects a rectangle. Let's say that it starts at
// the origin, or even off screen in your case
rect.transition()
.duration(250)
.attr('x', 10)
.attr('y', 20);
// this changes the x and y attributes
// of the rectangle to 10 and 20 respectively
// using a transition over a 250ms duration.
For the particular example that you gave, I would just change the y attribute so that it starts off screen (say y = -1 * height) and then transition it down to y = height or y = 200 or something like that.
Related
I have implemented the following version of the force diagram to show inter-cluster movement of nodes.
https://jsfiddle.net/Edwig_Noronha/67ey5rz0/
The nodes are grouped into four clusters. After the first initialization of the force diagram ends I call a function to transition the nodes from source to destination clusters.
function moveNodes() {
Object.keys(inputdata).forEach(function(key, index) {
svg.selectAll("circle.viewernodes" + index)
.each(function(d) {
d.type = d.destination;
});
});
viewersTransitioned = true;
force.start();
}
However, The stabilization of the first initialization of the force diagram takes about 35 seconds. Hence the transition happens after that much time.
Q1) is it possible to achieve a quicker stabilization of the force diagram with collision detection?
The transition of the nodes from source to destination clusters happens along a linear path.
Q2) Is it possible to make the nodes move along projectile paths?
To achieve quicker stabilization you can do one of two things in my experience.
Initialize the nodes X and Y values to be near to their end/goal state
Such as nodes[i].x = 500 etc, then calling the simulation start.
This would somewhat defeat the purpose of what you're trying to show in your example, unless you don't want the nodes to be shown moving to the groups and just be in them to begin with...
Stronger force
Have the force moving/pulling the nodes be stronger. This would require an essentially fundamental change to your approach to this example. Instead of just transitioning their positions, create custom forces within your force-layout that affect the appropriate nodes only based on their attributes. Place these forces in the center of your 'sorting circles' and they would attract the nodes appropriately.
See here for what something like this would look like: https://bl.ocks.org/mbostock/1021841
My code of svg.js use rotate action and move action, but two results dont have the same center coordinate
Here is my code
<body>
<div id="drawing"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/2.6.6/svg.js"></script>
<script type="text/javascript">
var draw = SVG('drawing')
var group_1 = draw.group()
var group_2 = draw.group()
var rect_1 = group_1.rect(50, 20).fill('#f06').center(50, 50)
var rect_2 = group_2.rect(50, 20).fill('#f09').center(50, 50)
rect_1.animate(1000).rotate(45).after(function(){
group_1.animate(1000).center(100, 100)})
group_2.animate(1000).center(100, 100)
</script>
</body>
In SVG groups needs to be understood as a grouping of elements. Basically the same as in photohop. You can select multiple elements and rotate or scale them at once.
That in turn means that groups do not have geometry on its own. It fully depends on the content where the group is visible on the screen (x, y) or how bif it is (with/height).
Thats the reason, why you cannot move a group. You only can transform it. To keep the api simple, svg.js gives you the handy move and center method which - under the hood - translate the group.
In your example, you move the group after you rotated it. But you do it with an absolute transformation. That means svg.js tries to incooperate the movement into the already present rotation. This maths does not goes off well sometimes.
To fix your problem you have to use relative transformations instead.
Which means the movement is ON TOP of the rotation. That also means that you have to figure out by how much you wanna move the group.
We are currently working on version 3 of svg.js which simplifies this transformation business alot. So I hope the final solution will be there soon
I want to create an animation of a path, like a journey/timeline. The user is shown a circle (eventually to be an image), when they click this circle the animation begins and shows a path animating/traveling to another circle with a fade in effect. I have attached an image which I think explains my idea best.
My question is - what would be the recommended way of doing this? css animation or is there a jquery library that would be helpful?
Thank you
I would take svg as base. With Inkscape (or similar) like that, you can design the path visually and include the blue circle.
Than you can inject the svg-code in your html like so (copy the svg code from the generated file):
<div class="svg-container">
<svg>…</svg>
</div>
Finally you can use javascript to reference the circle and the path:
var path = document.querySelector('.path'), //these selectors are just arbitrary
circle = document.querySelector('.circle');
To get a point on the path, you can use:
var point = path.getPointAtLength();
For animation, I assume that you basically know how to do that, since this would be too much to explain here. But lets say that p is the progress of you animation and will be in the range [0,1]. To calculate a point at a given p could be done like so:
let pointAtT = (path, t) => {
let l_total = path.getTotalLength();
return path.getPointAtLength(l_total * t);
}
Having that, you can use the x and y coordinate to manipulate the circle. Be aware of possibly applied transformations, that is why I recommend to transform everything to global coordinate space, calculate there and transform the result back to the item's coordinate space.
Documentation on mdn
There are a some svg libraries that might help you: svg.js, snap.svg and Raphaël.
http://d.pr/i/U5bb/4n26fLZr
Here's an example of a chart of ours. Pretty unreadable, yea? Is there an easy, dynamic way avoid this? I've tried implementing a dynamic height, but the problem is I can never seem to find the sweet spot that accommodates a smaller number of bars, and a larger number of bars. I feel like this has to be a problem that others have encountered before. Any help would be appreciated!
I accomplish this by doing the following:
1) determine height of non-data elements on the chart (ie, explicitly set the top and bottom margins, and add them together to get a base_height for the chart
2) determine how much space I want each bar to take, including bar width, and padding between bars, and set as my height_multiplier (I usually end up going for 20-25 pixels, personally)
3) on pulling my data, determine how many bars will be needed, and set as my bar_count
4) calculate: chart_height = base_height + (bar_count * height_multiplier)
5) Pass that value to the html to set the height of the chart's containing element.
If your data will vary so much that the chart height in your example works some times, but then you have as many data points as you've posted there, there simply will not be a 'sweet spot' that will handle both extremes well.
There is an example where we can click on a circle and see inner circles.
Also there are different examples of the force layout.
Is it possible to have a force layout and each node of it will/can be a circle with inner force layout?
So it will work as infinite zoom (with additional data loading) for these circles.
Any ideas/examples are welcome.
I would approach the problem like this: Build a force-directed layout, starting with one of the tutorials (maybe this one since it build something that uses circle packing for initialization). Add D3's zoom behavior.
var force = d3.layout.force()
// force layout settings
var zoom = d3.behavior.zoom()
// etc.
So far, so good. Except that the force layout likes to hang out around [width/2, height/2], but it makes the zooming easier if you center around [0, 0]. Fight with geometric zooming for a little while until you realize that this problem really demands semantic zooming. Implement semantic zooming. Go get a coffee.
Figure out a relationship between the size of your circles and the zoom level that will let you tell when the next level needs to be uncovered. Something like this:
// expand when this percent of the screen is covered
var coverageThreshold = 0.6;
// the circles should be scaled to this size
var maxRadius = 20;
// the size of the visualization
var width = 960;
// which means this is the magic scale factor
var scaleThreshold = maxRadius / (coverageThreshold * width)
// note: the above is probably wrong
Now, implement a spatial data filter. As you're zooming down, you basically want to hide any data points that have zoomed out of view so that you don't waste gpu time computing their representation. Also, figure out an algorithm that will determine which node the user is zooming in on. This very well might use a Voronoi tessalation. Learn way more than you thought you needed to about geometry.
One more math thing we need to work out. We'll have the child nodes take the place of the parent node, so we need to scale their size based on the total size of the parent. This is going to be annoying and require some tweaking to get right, unless you know the right algorithm... I don't.
// size of the parent node
var parentRadius = someNumberPossiblyCalculated;
// area of the parent node
var parentArea = 2 * Math.PI * parentRadius;
// percent of the the parent's area that will be covered by children
// (here be dragons)
var childrenCoverageRatio = 0.8;
// total area covered by children
var childrenArea = parentArea * childrenCoverageArea;
// the total of the radiuses of the children
var childTotal = parent.children
.map(radiusFn)
.reduce(function(a, b) { return a + b; });
// the child radius function
// use this to generate the child elements with d3
// (optimize that divide in production!)
var childRadius = function(d) {
return maxRadius * radiusFn(d) / childTotal;
};
// note: the above is probably wrong
Ok, now we have the pieces in place to make the magic sauce. In the zoom handler, check d3.event.scale against your reference point. If the user has zoomed in past it, perform the following steps very quickly:
hide the parent elements that are off-screen
remove the parent node that is being zoomed into
add the child nodes of that parent to the layout at the x and y location of the parent
explicitly run force.tick() a handful of times so the children move apart a bit
potentially use the circle-packing layout to make this process cleaner
Ok, so now we have a nice little force layout with zoom. As you zoom in you'll hit some threshold, hopefully computed automatically by the visualization code. When you do, the node you're zooming in on "explodes" into all it's constituent nodes.
Now figure out how to structure your code so that you can "reset" things, to allow you to continue zooming in and have it happen again. That could be recursive, but it might be cleaner to just shrink the scales by a few orders of magnitude and simultaneously expand the SVG elements by the inverse factor.
Now zooming out. First of all, you'll want a distinct zoom threshold for the reverse process, a hysteresis effect in the control which will help prevent a jumpy visualization if someone's riding the mousewheel. You zoom in and it expands, then you have to zoom back just a little bit further before it contracts again.
Okay, when you hit the zoom out threshold you just drop the child elements and add the parent back at the centroid of the children's locations.
var parent.x = d3.mean(parent.children, function(d) { return d.x; });
var parent.y = d3.mean(parent.children, function(d) { return d.y; });
Also, as you're zooming out start showing those nodes that you hid while zooming in.
As #Lars mentioned, this would probably take a little while.