Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

D3 scale.invert() isn't returning date for x

I am trying to use this method to get the closest y value, for the x value that is at the current mouse position.

My data set looks like this:

linedata = [{amount:100,date:'2015-11-2'},
            {amount:-1000,date:'2015-11-3'},
            {amount:5000,date:'2015-11-4'}
            ...
           ]

I have a line graph that is drawn fine, and I'm just playing with how scale.invert() and bisect work. I create a bisect function:

var bisectDate = d3.bisector(function(d) { return d.date; }).left;

Then, further down, after drawing the graph, there is a rectangle I put over the area where the graph is drawn

graph.append("rect")
         .attr("class", "overlay")
         .attr("width", w)
         .attr("height", h)
         .on("mouseover", function() { focus.style("display", null); })
         .on("mouseout", function() { focus.style("display", "none"); })
         .on("mousemove", mousemove);

Then the mousemove() function is defined like so:

function mousemove() {
    var xPosition = xScale.invert(d3.mouse(this)[0]), //<-- give me the date at the x mouse position
        closestElement = bisectDate(linedata, xPosition, 1), //<-- use the bisector to search the array for the closest point to the left and find that point given our mouse position
        d0 = linedata[closestElement - 1]
        d1 = linedata[closestElement],
        d = xPosition - d0.date > d1.date - xPosition ? d1 : d0;
    focus.attr("transform", "translate(" + xScale(+line_dateFormat.parse(d.date)) + "," + yScale(d.amount) + ")");
    focus.select("text").text(d.amount);
};

The problem I am having is, that this line in the mouseover() function:

var xPosition = xScale.invert(d3.mouse(this)[0])

is not returning a date, it is returning a number, like the pixel width of the rectangle. The next line, the bisect, cannot compare this number to the dates, and so ends up always being 1.

I am expecting xPosition to be either dates like this:

Sat Jul 19 2014 09:32:43 GMT+1000 (AEST)
Sat Jul 19 2014 07:28:21 GMT+1000 (AEST)
Sat Jul 19 2014 05:24:00 GMT+1000 (AEST)
Sat Jul 19 2014 03:50:43 GMT+1000 (AEST)
Fri Jul 18 2014 22:39:49 GMT+1000 (AEST)
Fri Jul 18 2014 14:53:27 GMT+1000 (AEST)

or like this:

1446382800000
1446382800000
1446382800000
1446382800000

but I'm getting stuff like this:

531.190359375
472.78146093749996
408.8622890625
358.1677734375
333.92257031249994

Obviously, I am doing it wrong, but I don't understand the fundamentals of why. Can anyone explain to me why im getting numbers and not dates?

like image 883
Sebastian Avatar asked Nov 24 '15 06:11

Sebastian


2 Answers

Using the method Nithin CV Poyyil recommended to determine the output of the actual data, to solve this problem, i had to do 2 things.

1 - in my backend app, convert the date from a string like '2015-11-02' to an epoch time in milliseconds, before sending it to the view to be used by d3.js. this made it easier to work with, probably should do this all the time from now on until forever.

2 - im bad at javascript and dont understand scope. so i just lumped (and duplicated) all the required functions into the mouseover function like this:

function mousemove() {
    var xScale = d3.time.scale()
    .domain([d3.min(linedata, function(d) { return d.date; }), d3.max(linedata, function(d) { return d.date; })])
    .range([0, w + 170] );

    var yScale = d3.scale.linear()
    .domain([amount_min, amount_max])
    .range([h, 0])
    .nice();

    var bisectDate = d3.bisector(function(d) { return d.date; }).left;
    var dateFormat = d3.time.format('%d. %b')

    var xPosition = xScale.invert(d3.mouse(this)[0]),//xScale.invert(d3.mouse(this)[0]), //<-- give me the date at the x mouse position
        closestElement = bisectDate(linedata, xPosition, 1), //<-- use the bisector to search the array for the closest point to the left and find that point given our mouse position
        d0 = linedata[closestElement - 1],
        d1 = linedata[closestElement],
        d = xPosition - d0.date > d1.date - xPosition ? d1 : d0;

    focus.attr("transform", "translate(" + xScale(d.date) + "," + yScale(d.amount) + ")");
    focus.select("text").text("Amount: " + d.amount + " Date: " + d.date);
};

not the 'right' way to do it but i get the result i want and thats more important to me. thanks for the help!

like image 125
Sebastian Avatar answered Oct 23 '22 11:10

Sebastian


Hopes this may help you,

Check whether your xScale function written correctly using d3.time scale.

Test xScale function by passing any date value in that range given. eg : xScale(new Date("01-01-1998")), result must be in between your range values given.

Test return value of xScale.invert(0 or 1) result must be a date value in the domain your given.

Based on bostock's link you are provided, code given below is working fine for me,

var width=500;
var xAxisScale = d3.time.scale().range([0, width]);
xAxisScale.domain([new Date("01-01-2014"),new Date("01-01-2015")]);
var rangeValue=xAxisScale(new Date("01-05-2014"))
var dateDomainValue=xAxisScale.invert(15);
console.log(rangeValue,dateDomainValue);

var mousemove=function(d){
 var xPosition = xAxisScale.invert(d3.mouse(this)[0])
 console.log("mousemove:",xPosition)
};

d3.selectAll("svg").append("rect")
         .attr("class", "overlay")
         .attr("width", 200)
         .attr("height", 300)
         .style("fill","gray")
         .on("mouseover", function() { console.log("mouseover") })
         .on("mouseout", function() { console.log("mouseout"); })
         .on("mousemove", mousemove);

HTML :

  <svg width="500" height="500"></svg>

Output:

mouseover
mousemove: Sun Jan 05 2014 09:07:12 GMT+0530 (India Standard Time)
mousemove: Sun May 25 2014 12:57:36 GMT+0530 (India Standard Time)
mousemove: Wed May 21 2014 03:50:24 GMT+0530 (India Standard Time)
mouseout
like image 1
Nithin CV Avatar answered Oct 23 '22 11:10

Nithin CV