Parameters needed for d3.mouse() - javascript

I am trying to get mouse coordinates relative to a group element. Here's the code:
var backRect = chart.append("g").attr("class", "rect");
var g = backRect.append('rect')
.style('stroke', 'none')
.style('fill', '#FFF')
.style('fill-opacity', 0)
.attr({
width: width,
height: height,
'pointer-events': 'none',
'class': 'backRect'
});
// the code below is inside another container's event; but I want mouse coordinates relative to the above rect, hence can't use d3.mouse(this)
// get mouse pointer location
var coordinates = [0, 0];
coordinates = d3.mouse(backRect); // d3.select(".rect") does not work either
but get the following error:
d3.min.js:1 Uncaught TypeError: n.getBoundingClientRect is not a function
According to the d3 mouse docs d3.mouse() takes a container which can be svg or g element.
What parameter should I pass to d3.mouse()? I tried d3.select(".rect") which is not working either.

Using d3.mouse(backRect.node()) did the trick.

You should be using d3.mouse() inside an event to get the values relative to the passed container.
Check this block
http://bl.ocks.org/hlucasfranca/f133da4493553963e710
svg.on("click", function() {
var coords = d3.mouse(this);
........
........
})

Related

Dragging element with d3.mouse(this) causes flickering of element

I have an absolutely positioned leading line within a relatively positioned div. I need to move the leading line horizonatally as the mouse moves using d3. Since d3.event requires the elements to be positioned within g elements, I am trying to use d3.mouse to move the leading line. However, the dragging behaviour is very erratic and flickers too much. I cannot seem to figure out what is causing this.
var drag = d3.behavior
.drag()
.on('drag', function(event) {
dragMoveNew(this);
})
d3.select(".grid").call(drag);
function dragMoveNew(elem) {
var x = d3.mouse(elem)[0];
$(elem).css('left', x);
}
Here is the jsfiddle link
https://jsfiddle.net/u4koenra/
Use d3.event.x
function dragMoveNew(elem) {
//var x = d3.mouse(elem)[0];
var x = d3.event.x;
$(elem).css('left', x);
}

How to make the circles disappear based on keyboard input?

based on this example: http://bl.ocks.org/d3noob/10633704 i wish to make an input on my keyboard (a number) and make the circles disappear with the help of array.slice(). Unfortunally it did not worked well. In my code, i created some circles based on the values of the array days. With the HTML part i am able to create a button, where i can make a number input. With the last part days.slice(nValue) i want that the input number is the same like the number inside the brackets of the slice() function, so the array days is getting shorter and automatically let circles based on the value of the array disappear. But unfortunally there is a mistake i made in this code. Can someone maybe be so kind and help? I am using D3 to solve this problem.
Thanks
<!DOCTYPE html>
<meta charset="utf-8">
<title>Input (number) test</title>
<p>
<label for="nValue"
style="display: inline-block; width: 120px; text-align: right">
angle = <span id="nValue-value"></span>
</label>
<input type="number" min="0" max="360" step="4" value="0" id="nValue">
</p>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var width = 600;
var height = 300;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var days = [7, 12, 20, 31, 40, 50];
console.log(days);
var circle = svg.selectAll("circle")
.data(days)
.enter().append("circle")
.attr("cy", 60)
.attr("cx", function(d, i) { return i * 100 + 40; })
.attr("r", function(d) { return Math.sqrt(d); });
d3.select("#nValue").on("input", function() {
update(+this.value);
});
// Initial update value
update(0);
function update(nValue) {
days.slice(nValue);
}
It took me a while to see what you're after here, and I might still be off a bit in my understanding.
The Problem
As I see understand it, you are modifying an array of data (with a select menu in this case), but the modified array does not appear to modify your visualization. Essentially, as "the array days is getting shorter ... let circles based on the value[s] of the array disappear."
Updating the visualization
To update the visualization you need to bind the new data to your selection. After this you can remove unneeded elements in the visualization, add new ones (not relevant to this question), or modify existing elements. Changing the data array by itself will not update the visualization. To have the visualization utilize the new information you need to bind that data to the selection:
circle.data(data);
Then you can remove the old items:
circle.exit().remove();
Then you can modify properties of the old items:
circle.attr('cx',function(d,i) {...
Your update function needs to at least update the data and remove unneeded elements.
Changing the Array
In the following snippet I append both a select menu and the circles with d3 based on the data in the array. Selecting an item in the menu will remove a circle:
var data = [10,20,30,40,50,60,70,80,90,100];
var color = d3.schemeCategory10; // color array built in
//// Add the select and options:
var select = d3.select('body')
.append('select')
.on('change',function() { update(this.value) });
var start = select.append('option')
.html("select: ");
var options = select.selectAll('.option')
.data(data)
.enter()
.append('option')
.attr('class','option')
.attr('value',function(d,i) { return i; })
.html(function(d) { return d; });
//// Add the circles (and svg)
var svg = d3.selectAll('body')
.append('svg')
.attr('width',500)
.attr('height',200);
var circles = svg.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx',function(d,i) { return i * 30 + 50; })
.attr('cy',50)
.attr('r',10)
.attr('fill',function(d,i) { return color[i]; });
// Update everything:
function update(i) {
data.splice(i,1); // remove that element.
// Update and remove option from the select menu:
options.data(data).exit().remove();
// Remove that circle:
circles.data(data).exit().remove();
circles.attr('cx',function(d,i) { return i * 30 + 50; })
.attr('fill',function(d,i) { return color[i]; });
// reset the select menu:
start.property('selected','selected');
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
There is a problem here, only the last circle and menu item is removed each time. Why? Imagine a four element array, if you remove the second item, d3 does not know that you removed the second item, you might have modified elements two and three and removed element four.
Since all your items are appended with their increment (which position they are in the array), and this doesn't account for holes that were created when other items were removed, you need to change the approach a little.
A solution
Instead of relying on the increment of an item in the array (as this will change every time an element that is before another element is removed from the array), you could use an id property in your data.
This would require restructuring you data a little. Something like:
var data = [ {id:1,value:1},{id2....
As the id property won't change, this makes a better property to set attributes. Take a look at the following snippet:
var data = [{id:0,value:10},{id:1,value:20},{id:2,value:23},{id:3,value:40},{id:4,value:50},{id:5,value:60},{id:6,value:70},{id:7,value:77},{id:8,value:86},{id:9,value:90}];
var color = d3.schemeCategory10; // color array built in
//// Add the select and options:
var select = d3.select('body')
.append('select')
.on('change',function() { update(this.value); } ); // add an event listener for changes
// append a default value:
var start = select.append('option')
.html("Select:");
var options = select.selectAll('.option')
.data(data)
.enter()
.append('option')
.attr('class','option')
.attr('value',function(d,i) { return i; })
.html(function(d) { return d.value; });
//// Add the circles (and svg)
var svg = d3.selectAll('body')
.append('svg')
.attr('width',500)
.attr('height',200);
var circles = svg.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx',function(d) { return d.id * 30 + 50; })
.attr('cy',50)
.attr('r',10)
.attr('fill',function(d) { return color[d.id]; });
// Update everything:
function update(i) {
data.splice(i,1); // remove the element selected
// Update and remove option from the select menu:
options.data(data).exit().remove();
// Remove that circle:
circles.data(data).exit().remove();
// update the options (make sure each option has the correct attributes
options.attr('value',function(d,i) { return i; })
.html(function(d) { return d.value; })
// Make sure circles are in the right place and have the right color:
circles.attr('cx',function(d) { return d.id * 30 + 50; })
.attr('fill',function(d) { return color[d.id]; });
// reset the default value so the change will work on all entries:
start.property('selected', 'selected');
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
Try changing your update function to this:
function update(nValue) {
days = days.slice(nValue);
}

Binding the same event to SVG g element and it's child

Using D3, I'm trying to bind a drag event to a 'g', group element, and a separate drag event to a child of this group. This seems to be causing issues as only group's drag event fires.
I've read through the specs a bit but don't see anything relating to this. Here's the code:
var group = that.vis.append('g')
.classed('dragger', true)
.attr('transform', 'translate(100, 0)')
.call(drag.on( 'drag', function() {...} )),
box = group.append('rect')
.attr('width', that.width * options.width)
.attr('height', that.height)
.classed('box', true);
var left = group.append('rect')
.attr('width', 4).attr('x', 0)
.attr('height', that.height)
.classed('drag-extend right', true)
.call(drag2.on('drag', function(){...}));
'that.vis' refers to the d3 selection containing the svg element. The d3 drag behaviors were created like so:
var drag = d3.behavior.drag()
.origin(function() {
var string = d3.select(this).attr('transform'),
//string = string.replace(/translate\(/, '');
array = string.match( /translate\((\d+), (\d+)\)/ );
return {
x : parseInt(array[1]),
y : parseInt(array[2])
}
}),
drag2 = d3.behavior.drag()
.origin(function() {
return {
x : d3.select(this).attr('x'),
y : 0
};
});
I need the elements to be grouped in order to move the whole group. My only thought is that when you attach an event handler to a SVG group it attaches that handler to all elements inside it? I'd like to be able to just stop propagation on the second element's drag handler so it doesn't bubble up to the 'g' parent however the second event doesn't seem to be attached at all.
Kind of at loss here, any help would be much appreciated...
UPDATE
So I was able to get this working but not in a way that I would of expected and I'm still interested in what exactly is going on here:
drag2 = d3.behavior.drag()
.origin(function() {
return {
x : d3.select(this).attr('x'),
y : 0
};
}).on( 'drag', shapeBox );
var left = group.append('rect')
.attr('width', 4).attr('x', 0)
.attr('height', that.height)
.classed('drag-extend right', true)
.call(drag2.on('dragstart', function(){...}));
function shapeBox() { ... }
For some reason adding the 'dragstart' handler somehow got the drag handler attached directly to the behavior to fire. This is strange because in looking at the d3 docs and from my knowledge of the DOM (which may be somewhat limited), I should have just been able to pass the 'drag2' variable to call.
Not sure what binding the extra listener did but somehow that got the drag to work.

Bar Chart outside bounds and mouseover event issue

Here is my dimple.js code, the bar chart that it produces is outside the bounds and touching Y-axis.
Mouseover event is not changing the color of the bars.
Below is the image
var myChart2 = new dimple.chart(svg,data);
myChart2.setBounds(750,50,550,250);
var x = myChart2.addTimeAxis( "x", "date", "%m/%d/%Y", "%d-%b");
x.floatingBarWidth = 21;
var y2= myChart2.addMeasureAxis("y","callperorder");
var y1= myChart2.addMeasureAxis("y","calls");
var bars = myChart2.addSeries("or", dimple.plot.bar,[x,y2]);
var lines= myChart2.addSeries("cl", dimple.plot.line,[x,y1]);
lines.lineMarkers= true;
myChart2.addLegend(750, 20, 300, 20, "right");
myChart2.assignColor("cl","rgb(99,39,29)");
myChart2.assignColor("or","rgb(99,89,219)");
myChart2.draw();
\\MOUSEOVER EVENT
bars.addEventHandler("mouseover", function( {d3.select(this).style("fill","green")});
You need to add the event handler before calling draw if you want to use the dimple method. Alternatively you could use the d3 method after draw.
bars.shapes.on("mouseover", function () {...});
NB. There's also a typo in your event declaration, it's missing the closing bracket after function (.
In order to avoid overlapping the edge of the chart you will need to manually set the x bounds:
x.overrideMin = d3.time.format("%m/%d/%Y").parse("12/31/2014");
x.overrideMax = d3.time.format("%m/%d/%Y").parse("01/11/2015");
Using whatever values you want of course;

d3.js force cancel a drag event

I have a simple drag event - and if a certain condition is met, I'd like to force cancel the drag currently under way (basically as if you were doing a mouseup).
Something like so:
var drag_behavior = d3.behavior.drag()
.on("drag", function() {
if(mycondition){
// cancel the drag event
}
});
EDIT:
the goal is simply to prevent people from dragging a world map outside certain boundaries in such a way that renders the map in mid-ocean (details below).
Current map code:
var width = window.innerWidth
var height = window.innerHeight
var projection = d3.geo.mercator()
.scale((width + 1) / 2 / Math.PI)
.translate([width / 2, height/1.5])
.precision(.1);
var path = d3.geo.path()
.projection(projection);
var drag_behavior_map = d3.behavior.drag()
.on("drag", function() {
drag_click = true //used later to prevent dragend to fire off a click event
d3.event.sourceEvent.stopPropagation();
// original idea
// if(worldmap_left_boundary > 0 || worldmap_right_boundary < screen.height){
// cancel or limit the drag here
// }
});
var svg = d3.select("#map").append("svg")
.attr("width", width)
.attr("height", height)
.call(drag_behavior_map)
Basically this should not be possible.
Inside the drag event function, you can use mycondition to decide whether or not to update the dragged element's position. Not updating the position essentially means stopping the drag.
You could also unsubscribe from the drag event:
var drag_behavior = d3.behavior.drag()
.on("drag", function() {
if(mycondition) {
// cancel the drag event
drag_behavior.on("drag", null);
}
});
Note, there would still be a dragend event, unless you unsubscribe that one too.
Alternatively –– though I'm not totally familiar with triggering native events and how that affects d3.behavior.drag() –– you can try triggering a native mouseup event and see what happens.
var drag_behavior = d3.behavior.drag()
.on("drag", function() {
if(mycondition) {
// "this" is the dragged dom element
this.dispatchEvent(new Event('mouseup'))
}
});

Categories