oymonk
oymonk

Reputation: 353

Search box that filters data then makes path appear?

I'm trying to make a d3 scatterplot that offers a searchbox on the webpage; when the user types a word into the search box, d3 filters the dataset based on the word, then creates a path through the filtered points on the scatterplot.

I've managed to get the code to the point that one can place the search term into the code, and get the desired effect (jsfiddle), but of course, I do not want my users to have to open up a text editor to search the dataset.

I like Gerardo Furtado's method of changing the attribute of the node

d3.select("button").on("click", function() {
  var txtName = d3.select("#txtName").node().value;
  circles.style("fill", function(d) {
  return d.doc === txtName ? "red" : "black";

Based on his answer, I have experimented with placing the code that appends the line to the graph within this piece of code. I get no errors, but I also get no line:

d3.select("button").on("click", function() {
  var txtName = d3.select("#txtName")
  chartGroup.selectAll(".line")
        .data(nest)
        .enter()
        .filter(function(d){return d.key == txtName;})
        .append("path")
          .attr("class","line")
          .attr("d",function(d) {return line(d.values)})
          .attr("stroke", function(d) {return colors(d.key)})
          .attr("stroke-width","2px")
          .style("stroke-dasharray", ("3, 3"))

I've also looked at Amber Thomas's multi-line graph, but I can't make sense of the code blocks that follow the filter function.

Any help appreciated

Upvotes: 1

Views: 108

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102194

There are several different solutions here. You can just draw all the paths, with zero opacity:

var lines = chartGroup.selectAll(".line")
  .data(nest)
  .enter()
  .append("path")
  .style("opacity", 0)
  .attr("class", "line")
  .attr("d", function(d) {
    return line(d.values)
  })
  .attr("stroke", function(d) {
    return colors(d.key)
  })
  .attr("stroke-width", "2px")
  .style("stroke-dasharray", ("3, 3"));

And then changing the opacity after the click:

d3.select("button").on("click", function() {
  var txtName = d3.select("#txtName").node().value;
  circles.style("fill", function(d) {
    return d.doc === txtName ? "red" : "black";
  })
  lines.style("opacity", function(d) {
    return d.key === txtName ? 1 : 0;
  })
})

Here is the resulting demo:

var parseDate = d3.timeParse("%m/%d/%Y");
mycolour = d3.rgb("#f7f7f7");

var doc = `date	number	creator		doc
6/16/2000	3	molly	3	rat
2/25/2002	4	may	2	cat
12/05/2004	3	molly	4	fish
07/06/2006	1	milly	1	dog
09/07/2003	4	may	4	fish
12/10/2001	4	may	3	rat
6/15/2005	2	maggie	3	rat
06/09/2004	1	milly	4	fish
10/05/2005	1	milly	3	rat
10/07/2003	4	may	1	dog
1/19/2009	4	may	2	cat
10/30/2007	1	milly	4	fish
8/13/2009	4	may	2	cat
9/30/2004	3	molly	1	dog
1/17/2006	4	may	3	rat
12/18/2009	3	molly	1	dog
11/02/2007	2	maggie	3	rat
4/17/2007	1	milly	4	fish`;

var data = d3.tsvParse(doc, function(d) {
  return {
    creator: d.creator,
    date: parseDate(d.date),
    number: Number(d.number),
    doc: d.doc
  };
});

var height = 300;
var width = 500;


function sortByDateAscending(a, b) {
  return a.date - b.date;
}

data = data.sort(sortByDateAscending);


margin = {
  top: 40,
  right: 50,
  bottom: 0,
  left: 50
};

var minDate = new Date(2000, 1, 1);
var maxDate = new Date(2011, 1, 1);



var y = d3.scalePoint()
  .domain(['may', 'milly', 'maggie', 'molly'])
  .range([height, 0])
  .padding(0.2);

var x = d3.scaleTime()
  .domain([minDate, maxDate])
  .range([0, width]);

var yAxis = d3.axisLeft(y);
var xAxis = d3.axisBottom(x);

var svg = d3.select("body").append("svg").attr("height", height + 100).attr("width", width + 100);

var chartGroup = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")")

var line = d3.line()
  .x(function(d) {
    return x(d.date);
  })
  .y(function(d) {
    return y(d.creator);
  });

var redBox = chartGroup.append("rect")
  .attr("y", 0)
  .attr("width", width)
  .attr("height", height)
  .attr("fill", mycolour)
  .append("g");

var nest = d3.nest()
  .key(function(d) {
    return d.doc;
  })
  .entries(data);



var colors = d3.scaleOrdinal()
  .domain(function(d) {
    return colors(d.key)
  })
  .range(["#e66101", "#fdb863", "#b2abd2", "#5e3c99"]);

var line = d3.line()
  .x(function(d, i) {
    return x(d.date);
  })
  .y(function(d, i) {
    return y(d.creator);
  });

var lines = chartGroup.selectAll(".line")
  .data(nest)
  .enter()
  .append("path")
  .style("opacity", 0)
  .attr("class", "line")
  .attr("d", function(d) {
    return line(d.values)
  })
  .attr("stroke", function(d) {
    return colors(d.key)
  })
  .attr("stroke-width", "2px")
  .style("stroke-dasharray", ("3, 3"));

var circles = chartGroup.selectAll("circle")
  .data(data)
  .enter().append("circle")
  .attr("cx", function(d) {
    return x(d.date);
  })
  .attr("cy", function(d) {
    return y(d.creator);
  })
  .attr("r", 4)
  .style("fill", "black");


d3.select("button").on("click", function() {
  var txtName = d3.select("#txtName").node().value;
  circles.style("fill", function(d) {
    return d.doc === txtName ? "red" : "black";
  })
  lines.style("opacity", function(d) {
    return d.key === txtName ? 1 : 0;
  })
})


chartGroup.append("g").attr("class", "x axis").attr("transform", "translate(0," + height + ")").call(d3.axisBottom(x).ticks(14));
chartGroup.append("g").attr("class", "y axis").call(d3.axisLeft(y).ticks(5));
path {
  fill: none;
}


/* circle {
  fill: #FF00FF;
  stroke:navy;
  stroke-width:2px; */


}
g.tick text y {
  font-size: 30px;
  font: Garamond;
}
g.tick text x {
  font-size: 10px;
  font: Garamond;
}
g.tick line {
  display: none;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<textarea id="txtName" name="txt-Name" placeholder="Search for something.."></textarea>
<button>Try it</button>

On the other hand, if you really want to put that block of code inside the listener, this is the problem:

When you do...

chartGroup.selectAll(".line")

...you are selecting an existing element, so your enter selection is empty. Instead of that, select some new class, like:

chartGroup.selectAll(".lineFiltered")

Here is the updated JSFiddle: https://jsfiddle.net/qmps19ox/1/

The problem with that approach, however, is that the lines will pile up, unless you create a proper enter/update/exit selections.

Upvotes: 2

Related Questions