d3.js mouseout会自动触发

时间:2017-09-11 14:08:21

标签: javascript css d3.js svg

我使用d3.js组件timeknots。该组件由linecircle元素组成。我稍微修改了这个组件。还有div用作标签的包装。如果用户将鼠标悬停在圈子元素上,则会显示带有标签的div元素。

如果用户悬停在圈子上,则会触发mouseover。然后自动触发mouseout圆圈处理程序和mouseenter标签,我不知道原因。

jsfiddle

HTML

<div id="timeline1" style="width:500px;"></div>

的javascript

var TimeKnots = {
  draw: function(id, events, options){
    var cfg = {
      width: 600,
      height: 200,
      radius: 10,
      lineWidth: 4,
      color: "#999",
      background: "#FFF",
      dateFormat: "%Y/%m/%d %H:%M:%S",
      horizontalLayout: true,
      showLabels: false,
      labelFormat: "%H:%M:%S",
      addNow: false,
      seriesColor: d3.scale.category20(),
      dateDimension: true,
      initTime: "0",
      duration: "0"
    };

    //default configuration overrid
    if(options != undefined){
      for(var i in options){
        cfg[i] = options[i];
      }
    }
    if(cfg.addNow != false){
      events.push({date: new Date(), name: cfg.addNowLabel || "Today"});
    }

    // sort events according to dates
    events.sort(function(a,b){
      // Turn your strings into dates, and then subtract them
      // to get a value that is either negative, positive, or zero.
        if (cfg.dateFormat.indexOf("%Y") === -1){
          return new Date("1971-01-01T" + a.date) - new Date("1971-01-01T" + b.date);
        }
        return new Date(a.date) - new Date(b.date);
      });

    var idIndex = "";

    var svg = d3.select(id).append('svg').attr("class", "timeline")
    .attr("width", cfg.width).attr("height", cfg.height).on("mouseover", function(d){
      if ($("#poi-"+idIndex).length > 0){
        console.log(idIndex + "hover", $("#poi-"+idIndex).is(":hover"));
      }

    });
    svg.append('defs')
    .append("linearGradient")
    .attr("id", "gradient").attr("gradientUnits", "userSpaceOnUse")
    .attr("y1", "0px").attr("y2", "0px")
    .attr("x1", "0px").attr("x2", "0px");
    svg.select('linearGradient').append("stop").attr("stop-color", "#1689fb");
    svg.select('linearGradient').append("stop").attr("stop-color", "rgb(126, 126, 126)");

    var tip = d3.select(id)
    .append('div')
    .attr("class","label-wrapper")
    .style("position", "absolute")
    .style("font-weight", "300")
    .style("color", "white");

    svg.append('g')
    .attr("stroke", "url(#gradient)")
    .attr("class", "wrapper-timeline");
    //Calculate times in terms of timestamps
    if(!cfg.dateDimension){
      var timestamps = events.map(function(d){
        if (cfg.dateFormat.indexOf("%Y") !== -1){
          return Date.parse(d.date);
        }
        return Date.parse("1971-01-01T" + d.date);
      });//new Date(d.date).getTime()});
      var maxValue = d3.max(timestamps);
      var minValue = d3.min(timestamps);
    }else{
      var timestamps = events.map(function(d){
        if (cfg.dateFormat.indexOf("%Y") !== -1){
          return Date.parse(d.date);
        }
        return Date.parse("1971-01-01T" + d.date);
      });//new Date(d.date).getTime()});
      var maxValue = d3.max(timestamps);
      var minValue = d3.min(timestamps);
    }
    var margin = (d3.max(events.map(function(d){return d.radius})) || cfg.radius)*1.5+cfg.lineWidth;
    var step = (cfg.horizontalLayout)?((cfg.width-2*margin)/(maxValue - minValue)):((cfg.height-2*margin)/(maxValue - minValue));
    var series = [];
    if(maxValue == minValue){step = 0;if(cfg.horizontalLayout){margin=cfg.width/2}else{margin=cfg.height/2}}

    linePrevious = {
      x1 : null,
      x2 : null,
      y1 : null,
      y2 : null
    }

    var minX = 0;
    var maxX = 0;
    var yPos = 0;
    var cxPoints = new Array();

    d3.select(id + " svg g")
    .append('g')
    .attr("clip-path", "url(#clipLine)")
    .attr("class", "wrapper-lines");

    svg.select('.wrapper-lines').selectAll("line")
    .data(events).enter().append("line")
    .attr("class", "timeline-line")
      .attr("x1", function(d, i){
                      var ret;
                      if(cfg.horizontalLayout){

                        var timeInMil = new Date();

                        if (cfg.dateFormat.indexOf("%Y") !== -1){
                          timeInMil = new Date(d.date).getTime();
                        } else {
                          timeInMil = new Date("1971-01-01T" + d.date).getTime();
                        }

                        var datum = (cfg.dateDimension) ? timeInMil : d.value;
                        ret = Math.floor(step*(datum - minValue) + margin)
                      }
                      else{
                        ret = Math.floor(cfg.width/2)
                      }
                      linePrevious.x1 = ret
                      if (i == 0){
                        minX= ret;
                      }
                      return ret;
                      })
    .attr("x2", function(d, i){
                      if (linePrevious.x1 != null){
                          if (i == 0){
                            maxX = linePrevious.x1;
                          }
                          return linePrevious.x1
                      }
                      if(cfg.horizontalLayout){
                        var timeInMil = new Date();

                        if (cfg.dateFormat.indexOf("%Y") !== -1){
                          timeInMil = new Date(d.date).getTime();
                        } else {
                          timeInMil = new Date("1971-01-01T" + d.date).getTime();
                        }

                        var datum = (cfg.dateDimension) ? timeInMil : d.value;
                        ret = Math.floor(step*(datum - minValue ))
                      }
                      return Math.floor(cfg.width/2);
                      })
    .attr("y1", function(d, i){
                      var ret;
                      if(cfg.horizontalLayout){
                        ret = Math.floor(cfg.height/2)
                      }
                      else{
                        var timeInMil = new Date();

                        if (cfg.dateFormat.indexOf("%Y") !== -1){
                          timeInMil = new Date(d.date).getTime();
                        } else {
                          timeInMil = new Date("1971-01-01T" + d.date).getTime();
                        }

                        var datum = (cfg.dateDimension) ? timeInMil : d.value;
                        ret = Math.floor(step*(datum - minValue)) + margin
                      }
                      linePrevious.y1 = ret
                      if (i === 0){
                        yPos = ret;
                      }
                      return ret
                      })
    .attr("y2", function(d){
                      if (linePrevious.y1 != null){
                        return linePrevious.y1
                      }
                      if(cfg.horizontalLayout){
                        return Math.floor(cfg.height/2)
                      }
                      var timeInMil = new Date();

                      if (cfg.dateFormat.indexOf("%Y") !== -1){
                        timeInMil = new Date(d.date).getTime();
                      } else {
                        timeInMil = new Date("1971-01-01T" + d.date).getTime();
                      }

                      var datum = (cfg.dateDimension) ? timeInMil : d.value;
                      return Math.floor(step*(datum - minValue))
                      })

    .style("stroke-width", cfg.lineWidth);

    var lastClipPathVal = "";

    var idIndexInOut = "";

    var timeout;
    var mouseOutTimer;

    svg.select(".wrapper-timeline").selectAll("circle")
    .data(events).enter()
    .append("circle")
    .attr("class", function (d, i) {
      return "timeline-event";
    })
    .attr("id", function (d, i) {
      return "knot-" + i;
    })
    .attr("r", function(d){if(d.radius != undefined){return d.radius} return cfg.radius})

    .style("stroke-width", function(d){if(d.lineWidth != undefined){return d.lineWidth} return cfg.lineWidth})
    .style("fill", "rgba(220,220,220, 0.01)")
    .attr("cy", function(d){
        if(cfg.horizontalLayout){
          return Math.floor(cfg.height/2)
        }
        var timeInMil = new Date();

        if (cfg.dateFormat.indexOf("%Y") !== -1){
          timeInMil = new Date(d.date).getTime();
        } else {
          timeInMil = new Date("1971-01-01T" + d.date).getTime();
        }

        var datum = (cfg.dateDimension) ? timeInMil : d.value;
        return Math.floor(step*(datum - minValue) + margin)
    })
    .attr("cx", function(d, i){

      var radius = this.getAttribute("r") != undefined ? this.getAttribute("r") : cfg.radius;

        var x = 0;

        if(cfg.horizontalLayout){
          var timeInMil = new Date();

          if (cfg.dateFormat.indexOf("%Y") !== -1){
            timeInMil = new Date(d.date).getTime();
          } else {
            timeInMil = new Date("1971-01-01T" + d.date).getTime();
          }

          var datum = (cfg.dateDimension) ? timeInMil : d.value;
          x = Math.floor(step*(datum - minValue) + margin);
          cxPoints.push(x);
          return x;
        }
        x = Math.floor(cfg.width/2);
        cxPoints.push(x);
        return x;
    }).on("mouseover", function(d){

      if(cfg.dateDimension){
        var format = d3.time.format(cfg.dateFormat);
        var datetime = format(new Date(d.date));
        var dateValue = (datetime != "")?(d.name +" <small>("+datetime+")</small>"):d.name;
      }else{
        var format = function(d){return d}; // TODO fix date formatting
        var datetime = d.value;
        var dateValue = d.name +" <small>("+d.value+")</small>";
      }

      idIndex = d3.select(this).attr("id").replace('knot-','');

      timeout = setTimeout(function(){
        $("#poi-"+idIndex).css({display: "flex"})
                          .hide()
                          .fadeIn()
                          .addClass("hovered");
      }, 400);

      lastClipPathVal = $(this).attr("clip-path");
      $(this).removeAttr("clip-path");

      d3.select(this)
      .style("fill", "white").transition()
      .duration(100).attr("r",  function(d){if(d.radius != undefined){return Math.floor(d.radius*1.8)} return Math.floor(cfg.radius*1.8)})

    })
    .on("mouseout", function(){

        idIndexInOut = d3.select(this).attr("id").replace('knot-','');
        clearTimeout(timeout);

        console.log("mouseout " + idIndexInOut);

        mouseOutTimer = setTimeout(function(){
          console.log("fadeoutTriggered " + idIndexInOut);
          $("#poi-"+idIndexInOut).fadeOut("fast");
        }, 400);

        d3.select(this)
        .style("fill", "rgba(220,220,220, 0.01)").transition()
        .duration(100).attr("r", function(d){if(d.radius != undefined){return d.radius} return cfg.radius})
        .attr("clip-path", lastClipPathVal);;

    });

    // svg.append("use").attr("xlink:href", '');

    var labelWrapper = d3.select('.label-wrapper')
    .style("width", cfg.width + "px")
    .style("left", "112px");
    var topOffset = -50;
    console.log(topOffset)
    var leftToRight = true;

    // Append POI's
    for (var idx = 0; idx < events.length; idx++) {

      if (cxPoints[idx] <= d3.select(id).node().getBoundingClientRect().width/2){
        leftToRight = true;
      } else {
        leftToRight = false;
      }

      var poiWrapper = labelWrapper.append('div')
      .attr("id", "poi-" + idx)
      .attr("class",function() {

        var className = "poi-wrapper";

        if (events[idx].type && events[idx].type === 'link'){
          return className += " poi-link";
        } else {
          return className;
        }
      });

      var poiImg = '';

      if (events[idx].type && events[idx].type === 'link'){
        poiImg = poiWrapper.append("div").attr("class", "background-img");

        var linkContent = poiWrapper.append("div").attr("class", "link-content")
        linkContent.append("span").attr("class", "poi-header").html(events[idx].heading);
        linkContent.append("span").attr("class", "poi-subheading").html(events[idx].subHeading);
        linkContent.append("ul")
        .attr("class", "poi-details")
        .append("li").html("<i class=\"fa fa-comment\" aria-hidden=\"true\"></i> " + events[idx].details);
      }

      if (events[idx].type !== 'topic'){
        if (leftToRight){
          if (poiImg.length > 0){
            poiWrapper.style("height", "100px");
            var ImgSvg = poiImg.append("svg").attr("class", "poi-image");

            ImgSvg.attr("class", "svg-defs").attr("width", "110px")
            .append('defs').append("clipPath").attr("id", "clip-triangle-"+[idx])
            .append("polygon").attr("points", "0,0 110,0 110,100 19,100 12,107 5,100 0,100 0,0");

            ImgSvg.append("rect").attr("class","svg-background").attr("clip-path", "url(#clip-triangle-"+[idx]+ ")").attr("width", "110px").attr("height", "110px");
            ImgSvg.append("image").attr("class","svg-image").attr("clip-path", "url(#clip-triangle-"+[idx]+ ")").attr("width", "110px").attr("height", "110px")
            .attr("xlink:href", events[idx].img);

            poiImg.style({'width': '110px','height': '110px'});
          }
        } else {
          poiWrapper.append("div").attr("class", "arrow-down-empty-anchor");
          if (poiImg.length > 0){
            poiWrapper.style("height", "100px");

            poiImg.style({'background': "url(" + events[idx].img + ")",
            'background-size': "100px 100px",
            'background-repeat': "no-repeat",
            'width': '100px',
            'height': '100px'});
          }
        }
      }

      $("#poi-" + idx).on(
      {
          mouseenter: function()
          {
            console.log("hover poi - clear timeout");
            // poiHovered = true;
            clearTimeout(mouseOutTimer);
          },
          mouseleave: function()
          {
            console.log("unhover poi");
            // poiHovered = false;
            mouseOutTimer = setTimeout(function(){
              console.log("fadeoutTriggered");
              $("#poi-"+idIndex).fadeOut("fast");
              // $("#poi-"+idIndex).css("zIndex", 0);
            }, 400);
          }
      });

      poiWrapper.style("position", "absolute")
      .style("color", "black")
      .style("top", function(d) {
        return topOffset - this.clientHeight - 30 + "px";
      })

      if (leftToRight){
        if (events[idx].type === 'topic'){

            poiWrapper.style("left", (cxPoints[idx]) +"px");
        } else {
          console.log(cxPoints[idx]);
            poiWrapper.style("left", (cxPoints[idx]- 120) +"px");
        }
      } else {
        var mainLineWidth = d3.select(".wrapper-timeline line").node().getBoundingClientRect().width;
        var vertOffset = mainLineWidth - cxPoints[idx];
        poiWrapper.style("right", vertOffset + "px")
      }

      poiWrapper.style("display", "none");
    }

    //Adding start and end labels
    if(cfg.showLabels != false){
      var measuredTime = new Date(null);

      if(cfg.duration){
        measuredTime.setSeconds(cfg.duration);
        var endString = measuredTime.toISOString().substr(12, 7);
      } else {
        var endString = duration;
      }

      svg.append("text")
         .text(endString).style({"font-size": "70%", "font-weight": "bold", "fill": "#7e7e7e"})
         .attr('class', 'duration')
         .attr("x", function(d){if(cfg.horizontalLayout){return  cfg.width -  d3.max([this.getBBox().width, (margin+this.getBBox().width/2)])} return Math.floor(this.getBBox().width/2)})
         .attr("y", function(d){if(cfg.horizontalLayout){return Math.floor(cfg.height/2+(margin+this.getBBox().height))}return cfg.height-margin+this.getBBox().height/2})
    }
  }
}



var kurbickFilms = [
  {
    type: "link",
    date: "00:00:00",
    img:
      "http://upload.wikimedia.org/wikipedia/en/thumb/6/6c/Seafarers_title.jpg/225px-Seafarers_title.jpg",
    heading: "Revenerace for Putin on the right buys trump cover",
    subHeading: "NYTIMES /  JEREMY PETERS",
    details: "3"
  },
  {
    type: "link",
    date: "00:10:25",
    img:
      "http://upload.wikimedia.org/wikipedia/en/thumb/6/6c/Seafarers_title.jpg/225px-Seafarers_title.jpg",
    heading: "Revenerace for Putin on the right buys trump cover",
    subHeading: "NYTIMES /  JEREMY PETERS",
    details: "3"
  },
  {
    type: "link",
    date: "00:10:25",
    img:
      "http://upload.wikimedia.org/wikipedia/en/thumb/6/6c/Seafarers_title.jpg/225px-Seafarers_title.jpg",
    heading: "Revenerace for Putin on the right buys trump cover",
    subHeading: "NYTIMES /  JEREMY PETERS",
    details: "3"
  },
  {
    type: "link",
    date: "01:11:24",
    img:
      "http://upload.wikimedia.org/wikipedia/en/thumb/6/6c/Seafarers_title.jpg/225px-Seafarers_title.jpg",
    heading: "Revenerace for Putin on the right buys trump cover",
    subHeading: "NYTIMES /  JEREMY PETERS",
    details: "3"
  }
];

TimeKnots.draw("#timeline1", kurbickFilms, {
  showLabels: true,
  dateFormat: "%H:%M:%S",
  color: "#7e7e7e",
  width: 800,
  height: 100,
  labelFormat: "%H:%M:%S",
  lineWidth: 2,
  radius: 6,
  duration: "4284.839125"
});

CSS

    svg.poi-image {
    position: absolute;
    height: 110px;
    width: 110px;
}

.poi-wrapper{
  display: flex;
  background: #61b4f7;
  z-index: 2;
}

.poi-intro, .poi-twitter, .poi-link, .poi-interview, .poi-add, .poi-comment{
  width: 300px;
}

1 个答案:

答案 0 :(得分:0)

有一个不可见的svg元素,它覆盖了circle元素。