I try to change the default scale of the NodeGraph here so it fits in the screen on first load. It is written with D3.js. Is there an initial variable, that defines the zoom?
You can set a suitable transform on the top-level g element, e.g. g.attr("transform", "scale(0.8)").
I managed to change the initial gravity, which makes the zoom not needed anymore:
// Gravity at pageload:
initial_gravity=0.07; // to apply a change here, enter `make` in you terminal
var force = d3.layout.force()
.charge( function (d) {
if (d.flags.client)
return -30 * chargeScale
return -100 * chargeScale
})
.gravity(initial_gravity)
....
afterwards you need to call make at the console to apply the changes
Related
I'm using the scrollama javascript library to write a "scrollytelling" article that involves transitioning D3 graphs in and out of view as the user scrolls. It is mostly working, but the graphs pile up on top of each other if I scroll too quickly.
Here is a jsfiddle based on this example by the scrollama author. In my example, the colored dots should fade in one at a time. If you were to scroll quickly to the end, the intermittent dots should not show up. The following snippets show how I've set up the transitions:
I define some functions that create my "graphs", and then call them.
var makeCircle0 = function(){
g.append("circle")
.attr("cx", 50)
.attr("cy", 100)
.attr("r", 20)
.attr("fill", "red")
.attr("class", "redcircle")
g.selectAll(".redcircle")
.attr("opacity", 0)
}
makeCircle0();
// Do this for makeCircle1, 2, and 3, also.
Then, I make functions to handle the transitions. This one says to make the red circle fade in and put the other circles at 0 opacity. I do this for all the circles/stages.
var showCircle0 = function(){
g.selectAll(".redcircle")
.transition()
.duration(1000)
.attr("opacity", 1)
g.selectAll(".yellowcircle").attr("opacity", 0)
g.selectAll(".greencircle").attr("opacity", 0)
g.selectAll(".bluecircle").attr("opacity", 0)
}
This section creates an array of my transition functions so that I can call them at specific steps in the page as you scroll. This is similar to how Jim Vallandingham handled his scroller.
var activateFunctions = [];
activateFunctions[0] = showCircle0;
activateFunctions[1] = showCircle1;
activateFunctions[2] = showCircle2;
activateFunctions[3] = showCircle3;
Finally, this calls the desired function at the right step in the page. Which it does... but not without halting the other transitions that got triggered in a previous step, resulting in multiple dots showing up at various stages.
function handleStepEnter(response) {
step.classed('is-active', function (d, i) {
return i === response.index;
})
figure.call(activateFunctions[response.index])
}
How can I prevent this?
If you need to interrupt a transition, d3-transition has a method for that:
selection.interrupt();
This will cancel a transition on an selection. If using named transitions you can specify a name by providing interrupt with one argument indicating the name of the transition to cancel.
If this is a generic version of your function to show an element:
function show() {
selectionToHide.attr("opacity",0);
selectionToShow.transition()
.attr("opacity",1);
}
Without using selection.interrupt you set the opacity to zero, and then the next tick of any transition in progress continues to update the opacity and finishes carrying out the transition. By adding interrupt we avoid that. Here's an updated fiddle.
However, there is another solution - we can apply another transition on the elements that we want to not show. To do so we just replace the transition with a new one:
function show() {
selectionToHide.transition()
.attr("opacity",0);
selectionToShow.transition()
.attr("opacity",1);
}
This will replace existing unnamed transitions (as yours are not named) and fade out elements, rather than simply hiding them all at once. Here's a fiddle of that. Of course if you have many elements this can be refined as to only apply a transition on any elements that are transitioning (not those that are already hidden) to reduce the amount of active transitions.
I haven't touched the scrolling, the circle that is shown should have its index match the displayed number, but it seems the number doesn't always match the scroll position, but this is a separate issue
I am trying to implement both zooming and brushing in my d3 (v4) chart.
I have got them both working separately, but my problem comes when I try to implement both features on the same chart.
My scenario is the following:
1. The user uses the brush to show a specific region of the chart.
2. They then zoom/pan, but this causes the view to jump back to the old location, because the stored zoom transform is not aware of the changes made by the brushing.
My understanding is that the current zoom transform (scale+translation) is stored inside the DOM element in an internal __zoom attribute. The zoom plugin automatically adjusts this whenever you interact with the element (e.g. by scrolling the mouse wheel).
I see that you can use d3.zoomTransform to get the current zoom transform for an element.
How can I reset/remove the stored zoom transform (e.g. after panning, so that any subsequent zooming carries on from where the brushing left off)?
Note: I don't want to have to change the zoom, but rather just update the stored zoom transform to treat that new scale as the "identity". This is important because I want to be able to smoothly transition from one scale to another when brushing etc.
The way I got around this in the end is:
in the zoom handler, use transform.rescaleX() to get a new transformed scale
Update the main scale's domain based on the transformed scale
Update the x-axis based on the scale
Reset the transform on the element to d3.zoomIdentity.
The key thing here is that after the scale has been updated, the stored transform on the DOM element is always put back to identity (i.e. scale=1, translate=0,0).
That means that we don't need to worry about brushing/zooming or any programatic changes to the scale on different elements won't conflict or have different values from each other. We effectively just keep applying very small scale factors to the element.
In terms of a code example, here are the relevant parts from my working chart:
// class contains:
// this.xScale - stored scale for x-axis
// this.xAxis - a d3 Axis
// this.xAxisElement - a d3 selection for the element on which the x-axis is drawn
// this.zoomX - a d3 ZoomBehavior
// this.chartElement - a d3 selection for the element on which the zooming is added
protected setupZooming(): void {
this.zoomX = d3.zoom().on('zoom', () => { this.onZoomX(); });
this.zoomXElement = this.xAxisElement
.append('rect')
.attr('fill', 'none')
.style('pointer-events', 'all')
.attr('width', this.width)
.attr('height', this.margin.bottom)
.call(this.zoomX);
}
onZoomX(): void {
const transform: d3.ZoomTransform = d3.event.transform;
if (transform.k === 1 && transform.x === 0 && transform.y === 0) {
return;
}
const transformedXScale = transform.rescaleX<any>(this.xScale);
const from = transformedXScale.domain()[0];
const to = transformedXScale.domain()[1];
this.zoomXTo(from, to, false);
this.chartElement.call(this.zoomX.transform, d3.zoomIdentity);
}
zoomXTo(x0: Date, x1: Date, animate: boolean): void {
const transitionSpeed = animate ? 750 : 0;
this.xScale.domain([x0, x1]);
this.xAxisElement.transition().duration(transitionSpeed).call(this.xAxis);
this.updateData(transitionSpeed);
}
updateData(transitionSpeed: number): void {
// ...
}
Apologies if this extract isn't easy to follow outside of the context of the rest of my code, but hopefully it is still helpful.
below is a normal example of tick function:
function tick(e) {
nodes
.each(cluster(10 * e.alpha * e.alpha));
}
who can tell me the definition of "e"? What properties does it have?
I can't find any description of "e", and what's the meaning of e.alpha. Yes, I used google but with no results.
Thanks for the help you gave below.
I'm copying some code, which use
var force = d3.layout.force()
.nodes(nodes)
.size([width, height])
.charge(-70)
.gravity(0.1)
.on("tick", tick)
.start();
so it's just the case you guess. I'm new to d3, a skim of force.layout API didn't give me any clue. Thanks for your precious time!
Without the full context of your "normal" function, this is a bit of a guess, but here goes:
tick is used in many contexts within d3. The inlcusion of alpha suggests that this is a force layout tick function, which is called by the force layout object on a tick event, in which case e would be the tick event object.
There is not a lot of documentation about the tick event, as most examples don't use it. If you inspect the source code, you will see
// A rudimentary force layout using Gauss-Seidel.
d3.layout.force = function() { //line 11
var force = {},
event = d3.dispatch("start", "tick", "end");
/* ... */
force.tick = function() { //line 58
// simulated annealing, basically
if ((alpha *= .99) < .005) {
event.end({type: "end", alpha: alpha = 0});
return true;
}
/* code to implement default force layout adjustments */
event.tick({type: "tick", alpha: alpha}); //line 128
};
/* ... */
return d3.rebind(force, event, "on"); //line 305
};
In other words, the tick event is one of three types of custom event created within the d3 source code using the d3.dispatch process. The tick event in particular is dispatched at the end of the internal tick function, and only contains one custom property: the current alpha parameter within the force layout. In order that these events actually go anywhere, the on method of the event dispatcher object is rebound on to the force layout object, so that the user can register listener functions for the custom events.
If all that is way too much d3 internals for you, just focus on these details:
e is a custom event object passed to your tick function every time it is called
e.alpha is the force layout's current alpha value, which by default starts at 0.1 and gets reduced (according to the friction parameter) at each tick until it drops below 0.005 and the layout freezes:
Internally, the layout uses a cooling parameter alpha which controls the layout temperature: as the physical simulation converges on a stable layout, the temperature drops, causing nodes to move more slowly. Eventually, alpha drops below a threshold and the simulation stops completely, freeing the CPU and avoiding battery drain. (From the API wiki for force.start)
In a previous post called "D3: How to create slow transition of Circles for nodes in Force Directed Graphs FDG?", I got a great answer for how to transition a single element (e.g. the radius for "just circles") in D3.
My followup question is now about how to transition "multiple D3 attributes" at the same time...
As a reminder, I'm using D3 generated Radio Buttons to toggle the size of Nodes in a FDG Layout (on mouse click) from a default size to a scaled magnitude. You can find the Radio Buttons in the upper left hand of the Node Cluster Diagram (http://nounz.if4it.com/Nouns/Applications/A__Application_1.NodeCluster.html)
The code that toggles the node circles between a default number and a scaled magnitude (now using transitions) looks as follows...
var densityControlClick = function() {
var thisObject = d3.select(this);
var typeValue = thisObject.attr("density_type");
var oppositeTypeValue = (function() {
if(typeValue=="On") {
return "Off";
} else {
return "On";
}
})();
var densityBulletSelector = "." + "densityControlBullet-" + typeValue;
var selectedBullet = d3.selectAll(densityBulletSelector);
selectedBullet.style("fill", "Black")
var oppositeDensityBulletSelector = "." + "densityControlBullet-" + oppositeTypeValue;
var selectedOppositeBullet = d3.selectAll(oppositeDensityBulletSelector);
selectedOppositeBullet.style("fill", "White")
if(typeValue=="On") {
var selectedNodeCircles = d3.selectAll("#NODE");
selectedNodeCircles.transition().duration(500).attr("r", function(d){ return rRange(d.rSize); });
}
else {
var selectedNodeCircles = d3.selectAll("#NODE"); selectedNodeCircles.transition().duration(500).attr("r", function(d) { if (d.id==focalNodeID) { return centerNodeSize; } else { return defaultNodeSize; } } );
}
}
Everything works great and you can see the slower node transitions when you select the radio buttons. However, I'd now like to learn how to transition multiple elements, such as the the radius and the edge lengths simultaneously, along with the theory behind doing so, in order to show off D3's dynamic nature.
My question is: Given that I already can successfully transition the radius of circles, how would I also transition other elements like the edge lengths based on attributes like "alpha", "friction", etc., and... what's the theory behind transitioning multiple elements (in other words, what does the code mean, in English)? The D3 API doesn't appear to clearly get into the theory behind transitioning multiple attributes, simultaneously.
So transitioning multiple attributes is the simple part of this question. Just like a regular selection you can set multiple attributes at a time on your transition:
selectedNodeCircles.transition().duration(500)
.attr("r", function(d){ return rRange(d.rSize); })
.attr("stroke", 'red');
This will transition your radius and your line colour. The transition is a property of the DOM element (in this case the circle) and it will transition as many DOM attributes as you like. The thing to remember is that there is only only one transition object on each DOM element. So if you create another you will overwrite the old one.
// This will NOT work
circles.transition().duration(1000).attr('r', 50);
// The radius transition will be overridden by the fill
// transition and so will not complete
circles.transition().duration(1000).attr('fill', 'red');
This can actually be quite useful because you don't have to worry about interrupting animations that are in progress and figure out how far along they are and then starting a new animation - this will generally be handled automatically.
In your case you want to transition edge lengths in your graph. These are determined by the positional attributes of the nodes. Judging by your finished product, these attributes are already being animated because you are updating the DOM on every iteration of the layout algorithm (not through transitions) probably in the tick() callback.
So you could use transitions inside your tick callback, which might look odd and may be a hassle to keep in synch with the radius transitions (you will have to set both attributes in the transition). But it might be just what you need.
Alternatively, if you can wait, don't update the DOM in the tick callback. Let the layout complete - it runs a lot faster when it is not rendering on each tick - and once it is complete you can animate the radius and x and y attributes to their final positions. Of course this means you'll want good starting positions.
I've been able to build a Force Directed Graph using a Force Layout. Most features work great but the one big issue I'm having is that, on starting the layout, it bounces all over the page (in and out of the canvas boundary) before settling to its location on the canvas.
I've tried using alpha to control it but it doesn't seem to work:
// Create a force layout and bind Nodes and Links
var force = d3.layout.force()
.charge(-1000)
.nodes(nodeSet)
.links(linkSet)
.size([width/8, height/10])
.linkDistance( function(d) { if (width < height) { return width*1/3; } else { return height*1/3 } } ) // Controls edge length
.on("tick", tick)
.alpha(-5) // <---------------- HERE
.start();
Does anyone know how to properly control the entry of a Force Layout into its SVG canvas?
I wouldn't mind the graph floating in and settling slowly but the insane bounce of the entire graph isn't appealing, at all.
BTW, the Force Directed Graph example can be found at: http://bl.ocks.org/Guerino1/2879486enter link description here
Thanks for any help you can offer!
The nodes are initialized with a random position. From the documentation: "If you do not initialize the positions manually, the force layout will initialize them randomly, resulting in somewhat unpredictable behavior." You can see it in the source code:
// initialize node position based on first neighbor
function position(dimension, size) {
...
return Math.random() * size;
They will be inside the canvas boundary, but they can be pushed outside by the force. You have many solutions:
The nodes can be constrained inside the canvas: http://bl.ocks.org/mbostock/1129492
Try more charge strength and shorter links, or more friction, so the nodes will tend to bounce less
You can run the simulation without animating the nodes, only showing the end result http://bl.ocks.org/mbostock/1667139
You can initialize the nodes position https://github.com/mbostock/d3/wiki/Force-Layout#wiki-nodes (but if you place them all on the center, the repulsion will be huge and the graph will explode still more):
.
var n = nodes.length; nodes.forEach(function(d, i) {
d.x = d.y = width / n * i; });
I have been thinking about this problem too and this is the solution I came up with. I used nodejs to run the force layout tick offline and save the resulting nodes data to a json file.
I used that as the new json file for the layout. I'm not really sure it works better to be honest. I would like hear about any solutions you find.