Search code examples
javascripthtmld3.jscanvasparallel-coordinates

Mousemove event won't fire on canvas even if z-index is max


I’m a bit at loss since a couple of weeks, trying to get a mousemove event to work on a set of layered canvases. Following advice in an earlier post, I confirmed that the event triggers properly only when the z-index of the targeted layer is on top (as demonstrated by this simple fiddle):

screenshot of working mousemove event

However, in the extended code I’m working with (d3 parcoords), despite having the same HTML structure as above, I can’t get the event to fire for canvases in the parallel coordinates chart on top.

screenshot of unsuccessful mousemove event

This bl.ocks shows the extended version, and how the event won't work even when the targeted layered canvas has the greatest z-index (although the event does work well in a simple canvas below the chart). I tried making a minimal example out of the parcoords file, but can’t manage to get a useful and working version given the number of interconnected functions.

I’m hoping someone who knows the original parcoords code might be able to clarify how exactly the chart's canvases are organized, and if something particular there might be causing the mousemove event not to work. Alternatively, maybe some experienced eyes might catch something I’m missing in the examples I posted. Any tips much appreciated!

Extract of code from d3.parcoords.js which generates the canvases:

var pc = function(selection) {
  selection = pc.selection = d3.select(selection);

  __.width = selection[0][0].clientWidth;
  __.height = selection[0][0].clientHeight;

  // canvas data layers
  ["marks", "foreground", "brushed", "highlight", "clickable_colors"].forEach(function(layer, i) {
    canvas[layer] = selection
      .append("canvas")
      .attr({
      id: layer, //added an id for easier selecting for mouse event 
      class: layer,
      style: "position:absolute;z-index: " + i
      })[0][0];
    ctx[layer] = canvas[layer].getContext("2d");
  });


  // svg tick and brush layers
  pc.svg = selection
    .append("svg")
      .attr("width", __.width)
      .attr("height", __.height)
      .style("font", "14px sans-serif")
      .style("position", "absolute")

    .append("svg:g")
      .attr("transform", "translate(" + __.margin.left + "," + __.margin.top + ")");

  return pc;
};

Function used to draw the squares and set the mousemove event:

//This custom function returns polyline ID on click, based on its HEX color in the hidden canvas "clickable_colors"
//Loosely based on http://jsfiddle.net/DV9Bw/1/ and https://stackoverflow.com/questions/6735470/get-pixel-color-from-canvas-on-mouseover
function getPolylineID() {

    function findPos(obj) {
        var curleft = 0, curtop = 0;
        if (obj.offsetParent) {
            do {
                curleft += obj.offsetLeft;
                curtop += obj.offsetTop;
            } while (obj = obj.offsetParent);
            return { x: curleft, y: curtop };
        }
        return undefined;
    }

    function rgbToHex(r, g, b) {
        if (r > 255 || g > 255 || b > 255)
            throw "Invalid color component";
        return ((r << 16) | (g << 8) | b).toString(16);
    }


    // set up some squares
    var my_clickable_canvas = document.getElementById('clickable_colors');
    var context = my_clickable_canvas.getContext('2d');
    context.fillStyle = "rgb(255,0,0)";
    context.fillRect(0, 0, 50, 50);
    context.fillStyle = "rgb(0,0,255)";
    context.fillRect(55, 0, 50, 50);

    $("#clickable_colors").mousemove(function(e) {
    //$(document).mousemove(function(e) {
    //debugger;
        var pos = findPos(this);
        var x = e.pageX - pos.x;
        //console.log(x)
        var y = e.pageY - pos.y;
        var coord = "x=" + x + ", y=" + y;
        var c = this.getContext('2d');
        var p = c.getImageData(x, y, 1, 1).data; 
        var hex = "#" + ("000000" + rgbToHex(p[0], p[1], p[2])).slice(-6);
        $('#status').html(coord + "<br>" + hex);
        console.log("Polyline's hex:" + hex)
    });
}

Solution

  • The svg is covering your canvases. The canvases and svg are all on the same level, but you have not set a z-index on the svg, and as it is rendered after all the canvases.
    Simply putting z-index: 0; on the svg in the page you linked fixed it for me in Chrome. It seems to simply be a problem with z-indexes.
    You should set both css position and z-index on all the canvases and the sgv on the same level.

    EDIT
    Sorry, I was wrong about it being just the z-index.
    I could get it to work by removing the following css.

    .parcoords > canvas {
        pointer-events: none;
    }
    

    But that seems to be in the library you are using, so just override it.

    .parcoords > canvas {
        pointer-events: auto;
    }