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.
Related
I am trying to implement in D3 v7 a dual-brush functionality, which:
with the standard mouse button brush allows to highlight the brushed dots in a scatter chart (as has been done many times)
with the mouse button brush while holding the control key (or the right mouse button or any other easy to implement key), allows to remove (ie make transparent) brushed dots in a scatter chart.
Functionality 1) already works but I struggle to add 2) at the same time.
In the post
Brush d3.js with right click
the brush.filter functionality is discussed as a potential solution at high level. So I thought about implementing it in the following manner:
var brush = d3.brush()
.filter(event => !event.ctrlKey)
.on("end", brushed) //Function to highlight
svg
.call(brush)
and
var brush2 = d3.brush()
.filter(event => event.ctrlKey)
.on("end", brushed2) //Function to make dots transparent
svg
.call(brush2)
It appears though that the svgs are drawn above each other an consequently only the last added layer is available to brush.
No luck either with something like
var brush = d3.brush()
.filter(event => !event.ctrlKey)
.on("end", brushed)
.filter(event => event.ctrlKey)
.on("end", brushed2)
svg
.call(brush) //Function with an if statement to decide highlight or removal
Understand I might need to specify "pointer-events","none" somewhere but haven't been able to fully figure this out by myself.
Any ideas welcome.
I am using this example as a basis for zoom in my project but I would like to restrict the zoom to only the buttons and double clicking. Ideally, using the scroll wheel should just continue scrolling down the page and not zoom in on the svg when the mouse is over it. Any help is appreciated!
For D3 4.0, there're two ways to prevent d3 zoom from responding to wheel event.
A:
zoom.filter(function() { return !event.button && event.type !== 'wheel'; })
// the `!event.button` expression is from the default filter, according to
// doc for zoom.filter([filter])
B:
svg.on("wheel.zoom", null);
// according to doc for zoom(selection): Internally, the zoom behavior
// uses selection.on to bind the necessary event listeners for zooming.
// The listeners use the name .zoom
It's still possible to pan(translate) with mouse dragging.
You can achieve this by:
var svg = d3.select("body").select("svg")
.attr("width", width)
.attr("height", height)
.append("g")
//.call(zoom) //remove the zoom listener from the group.
.append("g");
Next attach doublclick listener on the circle and the rectangle for zooming.
d3.selectAll("rect").on('dblclick', zoomClick)
d3.selectAll("circle").on('dblclick', zoomClick)
working code here
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.
From the d3.js bubbles example I have added the zoom + pan functions. Now I want to allow users to select a bubble on desktops and touch-screens. The dblclick and touchstart seem to be consistent for this.
I know d3 provides a way to cancel an event from .zoom but then it is disabled and doesn't do anything.
.call(d3.behavior.zoom().on("zoom", zoom)).on("dblclick", null);
Link: http://bl.ocks.org/4163494
What I have above sort of works but you will notice that a dblclick doesn't zoom at first but then it is revealed if you cause another zoom event. I am looking for the right way to do this.
To fix I left the
.on("zoom", redraw)
function where it was but moved the dblclick down to the circle, text, and tspan elements. From there they call a function that first stops propagation and then selects the circle.
http://jsfiddle.net/nnzS9/
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.