d3.slider and dates filtered on a timeline

2019-07-07 17:32发布

问题:

I have this example where some points get spawned over time with a slider. My version is almost working but something that I cant get my head around is this problem:

1) Shows hot is should be plotted from the beginning to end 2) Shows when is actual drage/click on the timeline to the end 3) Shows when I click to the end at once (no dragging)

the date is filtered in this function and it should work in my understanding. The example and my version are both using underscore.js where my knowledge is very limited. What is causing this problem that it does not filter properly to the end if i drag the slider handle?

var dateParser = d3.time.format("%d.%m.%Y").parse;
var minDate = dateParser("01.01.2015");
var maxDate = dateParser("02.12.2015");
console.log(minDate);
var secondsInDay = 60 * 60 * 24;

d3.select('#slider3').call(d3.slider()
    .axis(true)
    .min(minDate)
    .max(maxDate)
    // .min(function(d){
    //   var date = dateParser(d.properties.date);
    //   return date;
    // })
    // .max(function(d){
    //   var date = dateParser(d.properties.date);
    //   return date;
    // })
    .step(1)
    .on("slide", function(evt, value) {

    newData = site_data.features.filter(function(d){
      //console.log(new Date(value));
      return dateParser(d.properties.date) < new Date(value);
    });
        console.log("New set size ", newData.length);

    displaySites(newData);
  })

My full html looks something like this now

<!DOCTYPE html>
<head>
    <title>D3 Mapping Timeline</title>
<meta charset="utf-8">
<link rel="stylesheet" href="d3.slider.css" />
<style>

path {
  fill: none;
  stroke: #333;
  stroke-width: .5px;
}

.land-boundary {
  stroke-width: 1px;
}

.county-boundary {
  stroke: #ddd;
}

.site {
  opacity: 0.2;
  fill: #9cf;
}

#slider3 {
  margin: 20px 0 10px 20px;
  width: 900px;
}

svg {
  background: #eee;
}

.sphere {
  fill: rgb(92, 136, 255)
}

.land {
  fill: rgb(255, 239, 204)
}

.incident{
  fill:#07f5e7;
  opacity: 0.5;
}

.boundary {
  fill: none;
  stroke: rgb(224, 91, 49);
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.state {
  fill: #000;
}
.city{
  fill: #de1ae8;
}

.overlay {
  fill: none;
  pointer-events: all;
}
</style>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="d3.slider.js"></script>
</head>
<body>
<div id="slider3"></div>

<script>

var width = 1240,
  height = 720;
var projection = d3.geo.mercator()
    .translate([width / 2, height / 2])
    .scale((width - 1) / 2 / Math.PI);

var zoom = d3.behavior.zoom()
    .scaleExtent([3, 77])
    .on("zoom", zoomed);

var path = d3.geo.path()
    .projection(projection);

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

var g = svg.append("g");
var sites = svg.append("g");

svg.call(zoom)
    .call(zoom.event);

d3.json("countries.topo.json", function(error, world) {
	if (error) throw error;

  g.append("path")
      .datum({type: "Sphere"})
      .attr("class", "sphere")
      .attr("d", path);

  g.append("path")
      .datum(topojson.merge(world, world.objects.countries.geometries))
      .attr("class", "land")
      .attr("d", path);

  g.append("path")
      .datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }))
      .attr("class", "boundary")
      .attr("d", path);

//_______________________________________________________________________________________________________________________________________
//________________________________________________________________________________________________________________________________________


  d3.json("germany.topo.json", function(error, ger){
    if (error) throw error;
    var states = topojson.feature(ger, ger.objects.states),
        cities = topojson.feature(ger, ger.objects.cities );

    g.selectAll(".states")
        .data(states.features)
        .enter()
        .append("path")
        .attr("class", "state")
        .attr("class", function(d) { return "state " + d.id; })
        .attr("d", path);
    g.append("path")
        .datum(cities)
        .attr("d", path.pointRadius('0.05'))
        .attr("class", "city");
  });
});

function zoomed() {
  g.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
  sites.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}

d3.select(self.frameElement).style("height", height + "px");

d3.json("https://raw.githubusercontent.com/RitterLean/Slider-geojson-testing/master/vorfaelle.json", function(error, data){
    console.log(data.features[1].geometry.coordinates, "sad");
    window.site_data = data;

});


var displaySites = function(data) {
  //console.log(data)
  sites.selectAll(".site")
      .data(data)
      .enter()
      .append("circle")
      .attr("class", "site")
      .attr("cx", function(d) {
        var p = projection(d.geometry.coordinates);
        return p[0];
      })
      .attr("cy", function(d) {
        var p = projection(d.geometry.coordinates);
        return p[1]
      })
      .attr("r", 0)
      .transition().duration(400)
      .attr("r", 0.25);
    //  "".attr""

  sites.selectAll(".site")
        .data(data).exit()//remove the selection which are to be removed from dataset
      .transition().duration(200)
        .attr("r",0)
        .remove();

};


var dateParser = d3.time.format("%d.%m.%Y").parse;
var minDate = dateParser("01.01.2015");
var maxDate = dateParser("02.12.2015");
console.log(minDate);
var secondsInDay = 60 * 60 * 24;

d3.select('#slider3').call(d3.slider()
    .axis(true)
    .min(minDate)
    .max(maxDate)
    // .min(function(d){
    //   var date = dateParser(d.properties.date);
    //   return date;
    // })
    // .max(function(d){
    //   var date = dateParser(d.properties.date);
    //   return date;
    // })
    .step(1)
    .on("slide", function(evt, value) {

    newData = site_data.features.filter(function(d){
      //console.log(new Date(value));
      return dateParser(d.properties.date) < new Date(value);
    });
		console.log("New set size ", newData.length);

    displaySites(newData);
  })
);

</script>
</body>

ann all other files can be found on my repo because I don't know yet how to share it properly on stackoverflow.

I have three ideas where to begin. Maybe it is something to do with the min and max of the slider function in the main html but changing it just made the ticks of the axis disappear. It could also be something with the filtering inside the d3.slider.js (maybe underscore.js?). The data has only entries to the 2nd of december of this year so some points of interests wont' show because of the date so something connected to the .min .max maybe? So why is it not filtering like it is sopposed to?

Thank you in advance

回答1:

The slider is working perfectly and filtering it correctly.

Seems like the exit is identifying more data than it needs to remove., thus more points are not visible.

 sites.selectAll(".site")
        .data(data).exit()//remove the selection which are to be removed from dataset
      .transition().duration(200)
        .attr("r",0)
        .remove();

So I changed the displaySite function like this:

var displaySites = function(data) {
  //remove all the points 
  sites.selectAll(".site")
      .remove();
  //add all the filtered points    
  sites.selectAll(".site")
      .data(data)
      .enter()
      .append("circle")
      .attr("class", "site")
      .attr("cx", function(d) {
        var p = projection(d.geometry.coordinates);
        return p[0];
      })
      .attr("cy", function(d) {
        var p = projection(d.geometry.coordinates);
        return p[1]
      })
      .attr("r", 0)
      .transition().duration(40)
      .attr("r", 0.23);
};

Working code here

Another Way:

In the data pass the unique identifier for uniquely identifying object. Here below i am assuming each point will have a unique description please change it accordingly

var displaySites = function(data) {
  var sitedata = sites.selectAll(".site")
      .data(data, function(d){return d.properties.description});//uniquely identify each dataset point

  sitedata
      .enter()
      .append("circle")
      .attr("class", "site")
      .attr("cx", function(d) {
        var p = projection(d.geometry.coordinates);
        return p[0];
      })
      .attr("cy", function(d) {
        var p = projection(d.geometry.coordinates);
        return p[1]
      })
      .attr("r", 0)
      .transition().duration(40)
      .attr("r", 0.23);

    sitedata.exit()//remove the selection which are to be removed from dataset
      .transition().duration(200)
        .attr("r",0)
        .remove();

};

Working code here

Hope this helps