0

I basically copied the example https://bl.ocks.org/skokenes/a85800be6d89c76c1ca98493ae777572

Then I got the code to work with my data. So, I can now get the lasso to work.

But when I try to add back my old code for the circles to display a text-type tool tip, the lasso breaks. The code then puts the class variables such as not_possible or selected on the "text" elements rather than on the "circle" elements where they need to be.

I found that is the issue by using Chrome developer tools.

When the tool tips code is commented out, the lasso code works and the DOM looks like this:

<circle cx="854" cy="37" fill="red" r="7" class="selected"></circle>

When the tool tips code is live, the tool tips work but the lasso code doesn't work and the DOM looks like this:

<circle cx="854" cy="37" fill="red" r="4.9">
  <title  r="3.5" class> ==$0
      "curr = 89.7, prev = 89.5, geo = Alaska, measure = Percent Citizen, Born in the US"
  </title>
</circle>

I've tried changing the styles for the classes, for example, from ".possible" to "circle.possible" but that doesn't help. I've googled for suggestions but haven't found anything that I could make work. I've tried passing the circle selection thru lasso.items(circles) but that doesn't work.

This is the lasso code that does work: the troublesome ".append title" and "text" lines are commented out.

var margin = {top: 20, right: 15, bottom: 60, left: 60}
   , width = 960 - margin.left - margin.right
   , height = 960 - margin.top - margin.bottom;

var xScale = d3.scaleLinear()
         .domain([0, d3.max(data, function(d) { return d[1]; })])
         .range([0, width]);

var yScale = d3.scaleLinear()
         .domain([0, d3.max(data, function(d) { return d[0]; })])
         .range([height, 0]);

var svgArea = d3.select('.content')
         .append('svg')
         .attr('width', width + margin.right + margin.left)
         .attr('height', height + margin.top + margin.bottom)
         .attr('class', 'chart');

var main = svgArea.append('g')
    .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
    .attr('width', width)
    .attr('height', height)
    .attr('class', 'main');   

main.append('g')
        .attr('transform', 'translate(0,' + height + ')')
        .attr('class', 'main axis date')
        .call(d3.axisBottom(xScale)); 

main.append('g') 
        .append("text")
        .attr("x", width / 2)      
        .attr("y", height + margin.bottom - 10)
        .style("text-anchor", "middle")
        .style("font", "14px times")
        .text("Current X");

main.append('g')
        .attr('transform', 'translate(0,0)')
        .attr('class', 'main axis date')
        .call(d3.axisLeft(yScale));

main.append('g') 
        .append("text")
        .attr("transform", "rotate(-90)")
        .attr("x", 0 - (height / 2))      
        .attr("y", 0 - margin.left / 2)
        .style("text-anchor", "middle")
        .style("font", "14px times")
        .text("Previous Y");

var rScale = d3.scaleLinear()
        .domain([0, d3.max(data, function(d) { return d[1]; })])
        .range([ 4, 5 ]);

var lasso_start = function() {
  lasso.items()
    .attr("r",7)                 
    .classed("not_possible",true)
    .classed("selected",false)
    ;
};

var lasso_draw = function() {
  lasso.possibleItems()
    .classed("not_possible",false)
    .classed("possible",true)
    ;

  lasso.notPossibleItems()
    .classed("not_possible",true)
    .classed("possible",false)
    ;
};

var lasso_end = function() {
  lasso.items()
      .classed("not_possible",false)
      .classed("possible",false)
    ;

  lasso.selectedItems()
      .classed("selected",true)
      .attr("r", 7)
    ;

  lasso.notSelectedItems()
      .attr("r", 3.5)
    ;
};

var circles = main.selectAll("circle")
      .data(data)
      .enter().append("circle")
          .attr("cx", function (d,i) { return xScale(d[1]); } )
          .attr("cy", function (d) { return yScale(d[0]); } )
          .attr("fill", function (d) { if (d[1] > 75) { return "red"; } else { return "black"; } }) 
          .attr("r", function (d) { return rScale(d[1]); }) 
          //.append("title")                                  
          //.text(function(d) {
          //      return "curr = " + d[1] +
          //             ", prev = " + d[0] +
          //             ", geo = " + d[2] +
          //             ", measure = " + d[3]; 
          //      }) 
          ;

var lasso = d3.lasso()
      .items(circles)                                     
      .closePathDistance(75) // max distance for the lasso loop to be closed
      .closePathSelect(true) // can items be selected by closing the path?
      .targetArea(svgArea) // area where the lasso can be started
      .on("start",lasso_start) // lasso start function
      .on("draw",lasso_draw) // lasso draw function
      .on("end",lasso_end); // lasso end function

svgArea.call(lasso);

Why does including ".title" and ".text" cause a problem? And how do I solve it?

I don't think the problem is with the CSS, but here it is:

<style>
// styling for D3 chart
.chart {
    background: #fdfefe;
}

.main text {
    font: 10px sans-serif;
}

// styling for D3-lasso     

.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}

circle {
  fill-opacity: 0.4;
}

.dot {
  stroke: #000;
}

.lasso path {
  stroke: rgb(80,80,80);
  stroke-width: 2px;
}

.lasso .drawn {
  fill-opacity: 0.05 ;
}

.lasso .loop_close {
  fill: none;
  stroke-dasharray: 4,4;
}

.lasso .origin {
  fill: #3399FF;
  fill-opacity: 0.5;
}

.not_possible {
  fill: rgb(200,200,200);
}

.possible {
  fill: #EC888C;
}

.selected {
  fill: steelblue;
}


</style>

2 Answers2

0

The problem appears to be that lasso is adding a radius attribute to the title elements here:

lasso.notSelectedItems()
      .attr("r", 3.5)
    ;

resulting in all your not-selected elements, i.e., circles, and titles, having the attribute assigned, as your example suggests:

<title  r="3.5" class>

Rather than calling lasso's selected and notSelected to change the radius and css class of the desired items, use a filter on the items array itself:

  // Style the selected dots
  lasso.items().filter(function(d) {return d.selected===true})
    .classed(...)
    .attr("r",7);

  // Reset the style of the not selected dots
  lasso.items().filter(function(d) {return d.selected===false})
    .classed(...)
    .attr("r",3.5);

You can get as specific as you want with the return value, i.e., omit any nodes (like title nodes) you don't want affected by the rules you apply to the selection.

varontron
  • 1,120
  • 7
  • 21
  • I tried your "lasso.items().filter" suggestion, but I couldn't get it to work: my code still applied class to title elements rather than to circle elements. But I found a fix: a workaround that avoids appending titles: Mikhail Shabrikov's code near the bottom of the page about using [mouse events](https://stackoverflow.com/questions/10805184/show-data-on-mouseover-of-circle/) – cjones6cjones6 Jan 30 '20 at 18:28
  • Excellent! I have done similar to that workaround with a 3rd party tooltip (semantic). I'd have suggested that as well, but we are compelled in this forum to try to answer a specific question as specifically as possible. As we all know, there's often another way to do it. – varontron Jan 30 '20 at 18:43
0

The problem was that I couldn't get D3 lasso and my approach to tool tips to work together. I was appending a title element to each circle (point) on a scatter plot. This does NOT work:

var circles = main.selectAll("circle")
      .data(data)
      .enter().append("circle")
          .attr("cx", function (d,i) { return xScale(d[1]); } )
          .attr("cy", function (d) { return yScale(d[0]); } )
          .attr("fill", function (d) { if (d[1] > 75) { return "red"; } else { return "black"; } })
          .attr("r", function (d) { return rScale(d[1]); })
          .append("title")                                  
          .text(function(d) {
                return "curr = " + d[1] +
                       ", prev = " + d[0] +
                       ", geo = " + d[2] +
                       ", measure = " + d[3]; 
                }) 
          ;

I found a coding example by Mikhail Shabrikov that solved the issue by avoiding .append("title") altogether. This works:

A new CSS element:

.tooltip {
    position: absolute;
    z-index: 10;
    visibility: hidden;
    background-color: lightblue;
    text-align: center;
    padding: 4px;
    border-radius: 4px;
    font-weight: bold;
    color: black;
}

A new DIV element:

var tooltip = d3.select("body")
  .append("div")
  .attr('class', 'tooltip');

And mainly a modified circles element:

var circles = main.selectAll("circle")
      .data(data)
      .enter().append("circle")
          .attr("cx", function (d,i) { return xScale(d[1]); } )
          .attr("cy", function (d) { return yScale(d[0]); } )
          .attr("fill", function (d) { if (d[1] > 75) { return "red"; } else { return "black"; } })
          .attr("r", 5)
          .on("mouseover", function(d) {return tooltip.style("visibility", "visible")
               .text(
                         "curr = " + d[1] +
                         ", prev = " + d[0] +
                         ", geo = " + d[2] +
                         ", measure = " + d[3]
                   )
             })
          .on("mousemove", function() {
               return tooltip.style("top", (event.pageY - 30) + "px")
                 .style("left", event.pageX + "px");
             })
          .on("mouseout", function() {
               return tooltip.style("visibility", "hidden");
             })
          ;

Shabrikov's code is near the very bottom of this item: circles, tool tips, mouse events