Implemented force layout, here is the fiddle. However I want to disable the drag behaviour for the nodes, drag event will move the nodes across and set the nodes center to cursor position until mouseup event. Is there a way we can disable this? I tried removing the callback from the nodes as follows:
node.append("svg:circle").attr("r",5).style("fill", "#FE9A2E").on("mousedown.drag", null)
This didn't work. not sure if it will remove the callback from the node. also tried fixing the node position on drag event by setting fixed property of the node to true. But that fixes the node after it has been dragged. how can I stop the nodes from dragging?
The default drag behaviour is added to the nodes in the last part of this statement:
var node = vis.selectAll("g.node").data(nodeSet).enter()
.append("svg:g").attr("class", "node").call(force.drag);
Just remove the .call(force.drag) and you can no longer move around individual nodes. If you also want to get rid of the behaviour where you can move around the entire graph, it is part of the "zoom" behaviour added in the last line of this statement:
var vis = d3.select(".journalGraph").append("svg:svg")
.attr("width", w).attr("height", h)
.attr("pointer-events", "all").append('svg:g')
.call(d3.behavior.zoom().on("zoom", redraw)).append('svg:g');
Removing the call statement would get rid of both the pan and zoom functionality. See https://github.com/mbostock/d3/wiki/Zoom-Behavior for more on zooming.
Related
I have created a webpage with multiple charts using D3 and Object Oriented Programming.
Working code link--> https://codepen.io/mohan-ys/pen/LYLwqrK
The problem I am facing is that the tooltip is not at the mouse location, it is somewhere else.
I tried using d3.event.pageX & d3.event.pageY instead of vis.mouse[0] & vis.mouse[1] which is in the code above but it does not work.
I am getting the tooltip as shown. When the mouse is at the right end of the graph, the tool tip moves further right, it gets closer somewhere in the middle & it goes to the other side by the time the cursor is on the left end of the chart!
The page is resized, then it is a totally different behaviour!
Can anyone help get the tooltip right a the mouse pointer (top-left corner at the mouse pointer) for all graphs & even when the page is resized (the graphs scale with page resize).
The vertical line follows the mouse perfectly!, so, if there is another way of creating the tooltip instead of a div, that is also ok for me.
The underlying problem is addressed here but the answer doesn't directly solve your problem. Basically, there's a second and optional argument to d3.pointer called target such that:
If target is not specified, it defaults to the source event’s
currentTarget property, if available. If the target is an SVG element,
the event’s coordinates are transformed using the inverse of the
screen coordinate transformation matrix...
You can make use of this argument per below noting that it will break your vertical tracking line if you try and just update vis.mouse:
// mouse moving over canvas
vis.mouse = d3.pointer(event); // keep this for the vertical tracking line
vis.mouse2 = d3.pointer(event, d3.select(vis.chartLocation)); // <--- NEW VARIABLE!
Now vis.mouse2 has a relative x and y - so use them where you set the style of the div:
d3
.select(vis.chartLocation)
.selectAll("#tooltip")
.html((d, i) => {
vis.xDate = d.values[vis.idx - 1].date;
return vis.xDate.toLocaleDateString("pt-PT");
})
.style("display", "block")
.style("left", vis.mouse2[0] + "px") // use vis.mouse2
.style("top", vis.mouse2[1] + "px") // use vis.mouse2
The clue is in that your first selection is vis.chartLocation.
I want to make a D3 graph, which should be as follows:
When the html page is loaded, there will be a single node at a fixed location. Let us say top left. Let us call it template node and this node is non-movable.
When the user does mouse down on the template node, a new node is created at the same location as the template node and the user should be able to drag the new node to where he wants. The new node should remain exactly where the user moves it to.
At any time user should be able to move a node. Again the node should remain where the user leaves it.
User should be able to draw link between any two nodes. Let us assume that if he drags from one node to another without holding down ctrl key, then a link is drawn and if he drags while holding down the control key, then the node moves.
When a link is drawn between two nodes, then the nodes should not change positions.
When two nodes are linked and one of them is moved by dragging it, then the link should change in size and orientation as needed.
I am using force layout.
I am able to create a template node but it always goes to the center of the container - I think it is because the center of the container is the center of gravity. But not sure how to fix its position to the top left through code.
I can create links and new nodes. But the nodes move and links resize. May be it is because force layout tries to make link lengths equal to the link distance in the force layout. But I do not know how to use a function for link distance? I am even not sure if that will really help.
So what method should I use? Any idea?
For force layout, you can set the 'fixed' property of a node to true in order to prevent it from being affected by the simulation. After that, you should be able to set it's position manually. You might choose to do this in a function call:
function pinNode(node) {
node.fixed = true;
}
function unpinNode(node) {
node.fixed = false;
}
I believe you could get a node to the upper left corner with a call like this: pinNode(node, 0, 0). As long as the node has its fixed property set to true, it should remain unaffected by the sim. You might find this snippet from the documentation helpful; it describes how the fixed property is affected by force.drag:
Bind a behavior to nodes to allow interactive dragging, either using
the mouse or touch. Use this in conjunction with the call operator on
the nodes; for example, say node.call(force.drag) on initialization.
The drag event sets the fixed attribute of nodes on mouseover, such
that as soon as the mouse is over a node, it stops moving. Fixing on
mouseover, rather than on mousedown, makes it easier to catch moving
nodes. When a mousedown event is received, and on each subsequent
mousemove until mouseup, the node center is set to the current mouse
position. In addition, each mousemove triggers a resume of the force
layout, reheating the simulation. If you want dragged nodes to remain
fixed after dragging, set the fixed attribute to true on dragstart, as
in the sticky force layout example.
force.drag
Also see here: force layout nodes
If you want to use a function for link distance, include it when you create the force layout:
var force = d3.layout.force()
.size(width, height)
.linkStrength(0.5) // how much can link distance be overridedn by the simulation
.linkDistance(function() {return /* some evaluation */;});
// ...
// You might need to defer the calculation of linkDistance until later,
// such as in update(), since nodes might not have the properties
// that you need to check until that point:
function update() {
force
.nodes(nodes)
.links(links)
.linkDistance(function(link) {
// The function gets called for each link in the simulation.
// Each link will be connected to two nodes, source and target,
// which may be useful in determining link distance.
if (link.source.someProperty || link.target.somePropery) {
return /* something */;
} else {
return /* something else */;
}
});
}
I would like to use a D3.js brush to allow users to select a range of values on an axis. By default, clicking outside the brush clears it, so that no range is selected.
However, I would like to adjust this behaviour so that clicking outside the brush doesn't alter the brush extent. In effect, there should be no way to clear the brush, some range should always be selected.
I believe I have to hook into the brush event somehow to disable the clearing, but I don't really know how to go about that.
Here's an example of the kind of interface I'm talking about (Fiddle). When you click to the left or right of the black bar, the brush is cleared and the bar disappears.
How can I disable this behaviour?
d3 brush by design calls 'brushmove()' once a user presses a mouse on the brush element (i.e. on 'mousedown.brush' event).
If effectively leads to resetting the previous brush extent.
A possible workaround is to replace the original mousedown.brush handler with the custom one. The custom handler will only call the original handlers once the mouse was moved after initial mousedown.
var brushNode = chart.append("g")
.call(brush);
brushNode
.selectAll("rect")
.attr("y", -10)
.attr("height", 10);
// store the reference to the original handler
var oldMousedown = brushNode.on('mousedown.brush');
// and replace it with our custom handler
brushNode.on('mousedown.brush', function () {
brushNode.on('mouseup.brush', function () {
clearHandlers();
});
brushNode.on('mousemove.brush', function () {
clearHandlers();
oldMousedown.call(this);
brushNode.on('mousemove.brush').call(this);
});
function clearHandlers() {
brushNode.on('mousemove.brush', null);
brushNode.on('mouseup.brush', null);
}
})
See the demo.
I would like to add a behavior to a force directed graph layout in D3 in such a way that once dropped, a dragged-and-dropped svg node sticks in its place, no longer changing position no matter what else happens in the graph. I have done some reading about this API but I can't figure out a way to get that one working.
The problem I am trying to solve is allowing a user to "pick apart" a complex force graph.
Set the fixed property of the node to true on mousedown.
node.on("mousedown", function(d) { d.fixed = true; });
For example: http://bl.ocks.org/3750558
I want to draw a directed graph where any node can link to any other node (ie no defined hierarchy) and I was using the force layout engine but it doesn't support click event handling. I want to be able to click on a node and have that node centered and everything else laid out around it.
Is that possible in D3?
EDIT:
According to the API documentation for d3.force.layout:
force.on(type, listener)
Registers the specified listener to receive events of the specified
type from the force layout. Currently, only "tick" events are
supported
which suggests that simply adding a click event handler will not work.
Also, a tree layout needs (as far as I know) a hierarchy and my data is more tangled.
After puzzling away at this, I have something close to what I wanted so I'll share.
Firstly, There is no click handler for the layout engine but that is not needed for this; I wanted to click on a node and have that become fixed so I need a click handler on the node.
Secondly, there is a "fixed" property at the node level described in the d3 API for the force layout engine.
Combining these two, I can add click handler to the node that locks or unlocks the node's position.
Using the example here I add the following to the CSS:
circle:hover { fill: red; }
and change the circle definition from:
var circle = svg.append("svg:g").selectAll("circle")
.data(force.nodes())
.enter().append("svg:circle")
.attr("r", 6)
.call(force.drag);
to
var circle = svg.append("svg:g").selectAll("circle")
.data(force.nodes())
.enter().append("svg:circle")
.attr("r", 6)
.on("click", function(d){ d.fixed = 1 - d.fixed; force.start(); })
.call(force.drag);
and now, when I mouse-over a circle, it turns red (showing me I captured it) and then if I click, it locks it in place. I can then repeat this on other circles and drag them to where I want.