4
votes

I'm trying to adapt this D3js line chart example of mouse-over usage to my multi-line chart.

It seems that d3.mouse(this)[0] on the mousemove function generate the following error: "Cannot read property 'sourceEvent' of null".

1 Any idea why I get the null sourceEvent error ?

2 Any tips on how to adapt the mouse over example from a single line chart to a multi (n) line chart ?

Here is a jsfiddle to demonstrate the issue. ( And the solution )

var myApp = angular.module('app', []);
myApp.directive("lineChart", function() {
  return {
    restrict: 'E',
    scope: {
      data: '=',
      id: '@'
    },
    link: function (scope, element, attrs) {
      scope.$watch( 'data', function ( data ) {
        d3.select("#"+attrs.id).select("svg").remove();
        if (data) {
          var margin = {top: 20, right: 20, bottom: 30, left: 40},
              width = element[ 0 ].parentElement.offsetWidth - margin.left - margin.right,
              height = element[ 0 ].parentElement.offsetHeight - margin.top - margin.bottom;
          var parseDate = d3.time.format("%d-%b-%y").parse;
          var x = d3.time.scale()
              .range([0, width]);
          var y = d3.scale.linear()
              .range([height, 0]);
          var xAxis = d3.svg.axis()
              .scale(x)
              .orient("bottom")
              .innerTickSize(-height)
              .ticks(4)
              .outerTickSize(0)
              .tickPadding(5)
              .tickFormat(function(d) { return d3.time.format('%d/%m %H:%M')(new Date(d)); });
          var yAxis = d3.svg.axis()
              .scale(y)
              .orient("left")
              .innerTickSize(-width)
              .outerTickSize(0)
              .tickPadding(10);
          var line = d3.svg.line()
              .x(function(d) { return x(d[0]); })
              .y(function(d) { return y(d[1]); });
          var svg = d3.select(element[0]).append("svg")
              .attr("width", '100%')
              .attr("height", '100%')
              .attr('viewBox','0 0 '+ element[ 0 ].parentElement.offsetWidth +' '+ element[ 0 ].parentElement.offsetHeight )
            .append("g")
              .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
          var minX = d3.min(data, function (item) { return d3.min(item.values, function (d) { return d[0]; }) });
          var maxX = d3.max(data, function (item) { return d3.max(item.values, function (d) { return d[0]; }) });
          var minY = d3.min(data, function (item) { return d3.min(item.values, function (d) { return d[1]; }) });
          var maxY = d3.max(data, function (item) { return d3.max(item.values, function (d) { return d[1]; }) });
          x.domain([minX, maxX]);
          y.domain([0, maxY]);
          svg.append("g")
              .attr("class", "x axis")
              .attr("transform", "translate(0," + height + ")")
              .call(xAxis);
          svg.append("g")
              .attr("class", "y axis")
              .call(yAxis);
          var domaine = svg.selectAll(".domaine")
              .data(data)
              .enter().append("g")
              .attr("class", "domaine");
          domaine.append("path")
              .attr("class", "line")
              .attr("d", function (d) {
                  return line(d.values);
              })
              .style("stroke", function (d) {
                return d.color;
              });
          var focus = svg.append("g")
              .attr("class", "focus")
              .style("display", "none");
          focus.append("circle")
              .attr("r", 4.5);
          focus.append("text")
              .attr("x", 9)
              .attr("dy", ".35em");
          svg.append("rect")
              .attr("class", "overlay")
              .attr("width", width)
              .attr("height", height)
              .on("mouseover", function() { focus.style("display", null); })
              .on("mouseout", function() { focus.style("display", "none"); })
              .on("mousemove", mousemove());
          function mousemove() {              
            var x0 = x.invert(d3.mouse(this)[0]),
                i = bisectDate(data, x0, 1),
                d0 = data[i - 1],
                d1 = data[i];/*
                To adapt for multi line
                ,
                d = x0 - d0.date > d1.date - x0 ? d1 : d0;
            focus.attr("transform", "translate(" + x(d.date) + "," + y(d.close) + ")");
            focus.select("text").text(formatCurrency(d.close));*/
          }
        }
      });
    }
  };
});
function MainCtrl($scope) {
$scope.lineData = [{"key": "users","color": "#16a085",
"values": [[1413814800000,4034.418],[1413815400000,5604.155000000001]]},
{"key": "users 2","color": "#d95600",
"values": [[1413814800000,3168.183],[1413815400000,1530.8435]]}];
}
1

1 Answers

4
votes

When you write

.on("mousemove", mousemove());

it is immediately call the mousemove function and passes its return value as the listener function to the "mousemove" event. Because you are not setting correctly the this that is why d3.mouse(this) returns null.

The fix is very easy: just pass in your mousemove function as reference and don't call it:

.on("mousemove", mousemove);

However even after this fix you will still get an error in the fiddle because your bisectDate function is missing...