Parallel-Coordinates with tooltip

535 views Asked by At

I've been using those two libraries (they are the same but the latter uses d3 v5 and has a few added implementations: https://github.com/syntagmatic/parallel-coordinates and https://github.com/BigFatDog/parcoords-es/blob/master/demo/nutrients.html) for parcoords but in the last 3 days I havent found a way to show a tooltip when over the curves. I want to have the name of the object shown with mouseover but I dont know how to access the lines. I have been trying to call the svg or canvas (I think the lines are drawn there) to give them a .on and create a small label when mouseover accessing the data given to the parcoord but it hasnt been as easy as I just described. Here is an example of how my code looks like (its from the demo of the library but my code looks similar just a lil bit heavier):

<!doctype html>
<title>Linking to Data Table</title>
<link rel="stylesheet" type="text/css" href="./parcoords.css">
<link rel="stylesheet" type="text/css" href="style.css">
<style>
    body, html { margin: 0; padding: 0; width: 100%; height: 100%; }
    /* parcoords */
    #nutrients { height: 600px; width: 98%; }
    #nutrients text { font-size: 10px; }
</style>
<script src="lib/d3.v5.min.js"></script>
<script src="./parcoords.standalone.js"></script>
<script src="lib/underscore.js"></script>
<div id="nutrients" class="parcoords"></div>
<script id="brushing">
    var parcoords = ParCoords()("#nutrients");
    d3.csv('data/nutrients.csv').then(function(data) {
        var colorList = ['#3b73b9', '#aa71aa', '#ee3224', '#00aeef', '#f8981d',
            '#80bb42', '#aacdef', '#cacec2', '#aaa6ce', '#df9e9d', '#6ab2e7',
            '#ffdd00', '#9ac2b9', '#a7a9ac', '#bbbb42', '#e6a6ce'];
        var groups = _(data).chain()
            .pluck('group')
            .uniq();
        var colorScale = d3.scaleOrdinal().domain(groups).range(colorList);
        var color = function(d) { return colorScale(d.group); };
        parcoords
            .data(data)
            .color(color)
            .hideAxis(['name'])
            .margin({ top: 24, left: 60, bottom: 12, right: 0 })
            .mode("queue")
            .alpha(0.7)
            .render()
            .brushMode("1D-axes");  // enable brushing
    });
</script>

In this example it would be having the name for example cheese, appear when the mouse is over the respective line. I want it because i have a lot of lines and if i would use an axis for that, the names would overlap eachother and the user wouldnt be able to see them (in case you have another idea on how to do that)

I would be really thankfull if someone could put me out of this misery. Thanks!

edit, i think the lines are created here, ctx being the context 2d of the canvas:

  var singlePath = function singlePath(config, position, d, ctx) {
    Object.keys(config.dimensions).map(function (p) {
      return [position(p), d[p] === undefined ? getNullPosition(config) : config.dimensions[p].yscale(d[p])];
    }).sort(function (a, b) {
      return a[0] - b[0];
    }).forEach(function (p, i) {
      i === 0 ? ctx.moveTo(p[0], p[1]) : ctx.lineTo(p[0], p[1]);
    });
  };

  // draw single polyline
  var colorPath = function colorPath(config, position, d, ctx) {
    ctx.beginPath();
    if (config.bundleDimension !== null && config.bundlingStrength > 0 || config.smoothness > 0) {
      singleCurve(config, position, d, ctx);
    } else {
      singlePath(config, position, d, ctx);
    }
    ctx.stroke();
  };
1

There are 1 answers

0
magp On BEST ANSWER

For everyone with the same problem there is a link: http://bl.ocks.org/mostaphaRoudsari/b4e090bb50146d88aec4 that helps alot in this area.

If you are using the same library as me with d3.js v5 (the latter library link in my question) just add this to the parallel coordinates library (or the standalone):

function compute_centroids(config, position) {
        return function (row) {
            var centroids = [];
            var p = Object.keys(config.dimensions);
            var cols = p.length;
            var a = 0.5; // center between axes
            for (var i = 0; i < cols; ++i) {
                // centroids on 'real' axes
                var x = position(p[i]);
                var y = config.dimensions[p[i]].yscale(row[p[i]]);
                centroids.push(([x, y]));

                // centroids on 'virtual' axes
                if (i < cols - 1) {
                    var cx = x + a * (position(p[i + 1]) - x);
                    var cy = y + a * (config.dimensions[p[i + 1]].yscale(row[p[i + 1]]) - y);
                    if (config.bundleDimension !== null) {
                        var leftCentroid = config.clusterCentroids.get(config.dimensions[config.bundleDimension].yscale(row[config.bundleDimension])).get(p[i]);
                        var rightCentroid = config.clusterCentroids.get(config.dimensions[config.bundleDimension].yscale(row[config.bundleDimension])).get(p[i + 1]);
                        var centroid = 0.5 * (leftCentroid + rightCentroid);
                        cy = centroid + (1 - config.bundlingStrength) * (cy - centroid);
                    }
                    centroids.push(([cx, cy]));
                }
            }

        return centroids;}
};

And in the end where the constructor is add this line:

pc.compute_centroids = compute_centroids(config,position);

Then you should easily be able to use the funtions shown in this bl.ock link, in case you have any problems you can also ask