Bootstrap 浮点图上的弹出窗口

Bootstrap popover on flot graph

我正在尝试在图表上添加弹出窗口。它不起作用。

var datasets = [{
  "label": "Amend Existing Report",
  "data": [{
    "0": 1446422400000,
    "1": 0
  }, {
    "0": 1447027200000,
    "1": 0
  }, {
    "0": 1447632000000,
    "1": 0
  }, {
    "0": 1448236800000,
    "1": 0
  }, {
    "0": 1448841600000,
    "1": 0
  }, {
    "0": 1449446400000,
    "1": 1
  }, {
    "0": 1450051200000,
    "1": 0
  }, {
    "0": 1450656000000,
    "1": 0
  }, {
    "0": 1451260800000,
    "1": 0
  }, {
    "0": 1451865600000,
    "1": 0
  }, {
    "0": 1452470400000,
    "1": 0
  }, {
    "0": 1453075200000,
    "1": 1
  }, {
    "0": 1453680000000,
    "1": 1
  }, {
    "0": 1454284800000,
    "1": 0
  }],
  "idx": 0
}, {
  "label": "Investigate Report Problem",
  "data": [{
    "0": 1446422400000,
    "1": 1
  }, {
    "0": 1447027200000,
    "1": 0
  }, {
    "0": 1447632000000,
    "1": 2
  }, {
    "0": 1448236800000,
    "1": 4
  }, {
    "0": 1448841600000,
    "1": 0
  }, {
    "0": 1449446400000,
    "1": 1
  }, {
    "0": 1450051200000,
    "1": 0
  }, {
    "0": 1450656000000,
    "1": 2
  }, {
    "0": 1451260800000,
    "1": 0
  }, {
    "0": 1451865600000,
    "1": 0
  }, {
    "0": 1452470400000,
    "1": 0
  }, {
    "0": 1453075200000,
    "1": 5
  }, {
    "0": 1453680000000,
    "1": 0
  }, {
    "0": 1454284800000,
    "1": 0
  }],
  "idx": 1
}, {
  "label": "New Request (One Off Report)",
  "data": [{
    "0": 1446422400000,
    "1": 0
  }, {
    "0": 1447027200000,
    "1": 0
  }, {
    "0": 1447632000000,
    "1": 1
  }, {
    "0": 1448236800000,
    "1": 0
  }, {
    "0": 1448841600000,
    "1": 0
  }, {
    "0": 1449446400000,
    "1": 0
  }, {
    "0": 1450051200000,
    "1": 0
  }, {
    "0": 1450656000000,
    "1": 0
  }, {
    "0": 1451260800000,
    "1": 0
  }, {
    "0": 1451865600000,
    "1": 0
  }, {
    "0": 1452470400000,
    "1": 0
  }, {
    "0": 1453075200000,
    "1": 0
  }, {
    "0": 1453680000000,
    "1": 1
  }, {
    "0": 1454284800000,
    "1": 0
  }],
  "idx": 2
}, {
  "label": "New Request (Regular Report)",
  "data": [{
    "0": 1446422400000,
    "1": 4
  }, {
    "0": 1447027200000,
    "1": 0
  }, {
    "0": 1447632000000,
    "1": 2
  }, {
    "0": 1448236800000,
    "1": 2
  }, {
    "0": 1448841600000,
    "1": 0
  }, {
    "0": 1449446400000,
    "1": 1
  }, {
    "0": 1450051200000,
    "1": 0
  }, {
    "0": 1450656000000,
    "1": 0
  }, {
    "0": 1451260800000,
    "1": 1
  }, {
    "0": 1451865600000,
    "1": 1
  }, {
    "0": 1452470400000,
    "1": 0
  }, {
    "0": 1453075200000,
    "1": 3
  }, {
    "0": 1453680000000,
    "1": 2
  }, {
    "0": 1454284800000,
    "1": 0
  }],
  "idx": 3
}, {
  "label": "Other",
  "data": [{
    "0": 1446422400000,
    "1": 0
  }, {
    "0": 1447027200000,
    "1": 0
  }, {
    "0": 1447632000000,
    "1": 2
  }, {
    "0": 1448236800000,
    "1": 4
  }, {
    "0": 1448841600000,
    "1": 2
  }, {
    "0": 1449446400000,
    "1": 0
  }, {
    "0": 1450051200000,
    "1": 2
  }, {
    "0": 1450656000000,
    "1": 0
  }, {
    "0": 1451260800000,
    "1": 0
  }, {
    "0": 1451865600000,
    "1": 0
  }, {
    "0": 1452470400000,
    "1": 3
  }, {
    "0": 1453075200000,
    "1": 0
  }, {
    "0": 1453680000000,
    "1": 3
  }, {
    "0": 1454284800000,
    "1": 0
  }],
  "idx": 4
}, {
  "label": "Special Events",
  "data": [{
    "0": 1446422400000,
    "1": 0
  }, {
    "0": 1447027200000,
    "1": 0
  }, {
    "0": 1447632000000,
    "1": 0
  }, {
    "0": 1448236800000,
    "1": 1
  }, {
    "0": 1448841600000,
    "1": 0
  }, {
    "0": 1449446400000,
    "1": 3
  }, {
    "0": 1450051200000,
    "1": 1
  }, {
    "0": 1450656000000,
    "1": 0
  }, {
    "0": 1451260800000,
    "1": 0
  }, {
    "0": 1451865600000,
    "1": 0
  }, {
    "0": 1452470400000,
    "1": 0
  }, {
    "0": 1453075200000,
    "1": 0
  }, {
    "0": 1453680000000,
    "1": 0
  }, {
    "0": 1454284800000,
    "1": 0
  }],
  "idx": 5
}];

var ticks = [];
for (var i = 0; i < datasets[0].data.length; i++) {
  ticks.push(datasets[0].data[i][0]);
}

var options = {
  "legend": {
    "position": "ne",
    "noColumns": 6
  },
  "yaxis": {
    "min": 0
  },
  "xaxis": {
    "mode": "time",
    "timeformat": "%d %b",
    //    "tickSize": [7, "day"],
    ticks: ticks,
    "min": 1446163200000,
    "max": 1454544000000 // 1454284800000
  },
  "grid": {
    "clickable": true,
    "hoverable": true
  },
  "series": {
    "stack": true,
    "bars": {
      "show": true,
      "barWidth": 181440000.00000003,
      align: 'center'
    }
  }
};

$.plot($('#CAGraph'), datasets, options);




$("#CAGraph").bind("plothover",function(event, pos, item) {

 if (item) {
//console.log(item);
   var epoch = new Date(item.datapoint[0]);
   var percent = item.datapoint[1].toFixed(0);
   $('#tooltip').attr("data-original-title", item.series.label);
   $('#tooltip').attr("data-content", (percent) + "<br>Total: " + item.datapoint[1]);
   $("#tooltip").popover("show");
   
   $("#tooltip").popover({
    html: true,
    title : function() {
             return $(".popover-title").html();
    },
    content : function() {
             return  $(".popover-content").html();
    }
    
   });
   $(".popover").css({
    top : item.pageY,
    left : item.pageX + 10
   });
   $(".popover.right>.arrow").css({
    top : "20%",
   });

  } else {
   $('#tooltip').attr("title","");
   $('#tooltip').attr("data-content", "");
   
   $("#tooltip").popover("hide");
  } 
  
  });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="https://rawgit.com/flot/flot/master/jquery.flot.js"></script>
<script src="https://rawgit.com/Codicode/flotanimator/master/jquery.flot.animator.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/flot/0.8.3/jquery.flot.time.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/flot/0.8.3/jquery.flot.stack.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/flot.tooltip/0.8.5/jquery.flot.tooltip.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.11.2/moment.min.js"></script>

<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" integrity="sha256-7s5uDGW3AHqw6xtJmNNtr+OBRJUlgkNJEo78P4b0yRw= sha512-nNo+yCHEyn0smMxSswnf/OnX6/KwJuZTlNZBjauKhTK0c+zT+q5JOCx0UFhXQ6rJR9jg6Es8gPuD2uZcYDLqSw==" crossorigin="anonymous">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha256-KXn5puMvxCw+dAYznun+drMdG1IFl3agK0p/pqT9KAo= sha512-2e8qq0ETcfWRI4HJBzQiA3UoyFk6tbNyG+qSaIBZLyW9Xf3sWZHN/lxe9fTh1U45DpPf07yj94KsUHHWe4Yk1A==" crossorigin="anonymous"></script>



<div id="choices_CAGraph"></div>
<div id="CAGraph" style="width:910px;height:400px"></div>
<div id=tooltip class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>

工具提示 div 的 ID 在 HTML 中是错误的:tootltip 而不是 tooltip

并且在 JavaScript 中图表容器的 ID 是错误的:$("CAGraph").bind("plothover", ... 而不是 $("#CAGraph").bind("plothover",...

修复这两个错误后,弹出窗口显示但没有内容。
更新: 我让弹出窗口显示一些内容,但无法让它始终如一地工作。这可能是因为 popover show method is asnyc:

.popover('show')

Reveals an element's popover. Returns to the caller before the popover has actually been shown (i.e. before the shown.bs.popover event occurs). This is considered a "manual" triggering of the popover. Popovers whose both title and content are zero-length are never displayed.

回退到手动生成工具提示,但效果很好。查看更新后的代码片段:

var datasets = [{
  "label": "Amend Existing Report",
  "data": [{
    "0": 1446422400000,
    "1": 0
  }, {
    "0": 1447027200000,
    "1": 0
  }, {
    "0": 1447632000000,
    "1": 0
  }, {
    "0": 1448236800000,
    "1": 0
  }, {
    "0": 1448841600000,
    "1": 0
  }, {
    "0": 1449446400000,
    "1": 1
  }, {
    "0": 1450051200000,
    "1": 0
  }, {
    "0": 1450656000000,
    "1": 0
  }, {
    "0": 1451260800000,
    "1": 0
  }, {
    "0": 1451865600000,
    "1": 0
  }, {
    "0": 1452470400000,
    "1": 0
  }, {
    "0": 1453075200000,
    "1": 1
  }, {
    "0": 1453680000000,
    "1": 1
  }, {
    "0": 1454284800000,
    "1": 0
  }],
  "idx": 0
}, {
  "label": "Investigate Report Problem",
  "data": [{
    "0": 1446422400000,
    "1": 1
  }, {
    "0": 1447027200000,
    "1": 0
  }, {
    "0": 1447632000000,
    "1": 2
  }, {
    "0": 1448236800000,
    "1": 4
  }, {
    "0": 1448841600000,
    "1": 0
  }, {
    "0": 1449446400000,
    "1": 1
  }, {
    "0": 1450051200000,
    "1": 0
  }, {
    "0": 1450656000000,
    "1": 2
  }, {
    "0": 1451260800000,
    "1": 0
  }, {
    "0": 1451865600000,
    "1": 0
  }, {
    "0": 1452470400000,
    "1": 0
  }, {
    "0": 1453075200000,
    "1": 5
  }, {
    "0": 1453680000000,
    "1": 0
  }, {
    "0": 1454284800000,
    "1": 0
  }],
  "idx": 1
}, {
  "label": "New Request (One Off Report)",
  "data": [{
    "0": 1446422400000,
    "1": 0
  }, {
    "0": 1447027200000,
    "1": 0
  }, {
    "0": 1447632000000,
    "1": 1
  }, {
    "0": 1448236800000,
    "1": 0
  }, {
    "0": 1448841600000,
    "1": 0
  }, {
    "0": 1449446400000,
    "1": 0
  }, {
    "0": 1450051200000,
    "1": 0
  }, {
    "0": 1450656000000,
    "1": 0
  }, {
    "0": 1451260800000,
    "1": 0
  }, {
    "0": 1451865600000,
    "1": 0
  }, {
    "0": 1452470400000,
    "1": 0
  }, {
    "0": 1453075200000,
    "1": 0
  }, {
    "0": 1453680000000,
    "1": 1
  }, {
    "0": 1454284800000,
    "1": 0
  }],
  "idx": 2
}, {
  "label": "New Request (Regular Report)",
  "data": [{
    "0": 1446422400000,
    "1": 4
  }, {
    "0": 1447027200000,
    "1": 0
  }, {
    "0": 1447632000000,
    "1": 2
  }, {
    "0": 1448236800000,
    "1": 2
  }, {
    "0": 1448841600000,
    "1": 0
  }, {
    "0": 1449446400000,
    "1": 1
  }, {
    "0": 1450051200000,
    "1": 0
  }, {
    "0": 1450656000000,
    "1": 0
  }, {
    "0": 1451260800000,
    "1": 1
  }, {
    "0": 1451865600000,
    "1": 1
  }, {
    "0": 1452470400000,
    "1": 0
  }, {
    "0": 1453075200000,
    "1": 3
  }, {
    "0": 1453680000000,
    "1": 2
  }, {
    "0": 1454284800000,
    "1": 0
  }],
  "idx": 3
}, {
  "label": "Other",
  "data": [{
    "0": 1446422400000,
    "1": 0
  }, {
    "0": 1447027200000,
    "1": 0
  }, {
    "0": 1447632000000,
    "1": 2
  }, {
    "0": 1448236800000,
    "1": 4
  }, {
    "0": 1448841600000,
    "1": 2
  }, {
    "0": 1449446400000,
    "1": 0
  }, {
    "0": 1450051200000,
    "1": 2
  }, {
    "0": 1450656000000,
    "1": 0
  }, {
    "0": 1451260800000,
    "1": 0
  }, {
    "0": 1451865600000,
    "1": 0
  }, {
    "0": 1452470400000,
    "1": 3
  }, {
    "0": 1453075200000,
    "1": 0
  }, {
    "0": 1453680000000,
    "1": 3
  }, {
    "0": 1454284800000,
    "1": 0
  }],
  "idx": 4
}, {
  "label": "Special Events",
  "data": [{
    "0": 1446422400000,
    "1": 0
  }, {
    "0": 1447027200000,
    "1": 0
  }, {
    "0": 1447632000000,
    "1": 0
  }, {
    "0": 1448236800000,
    "1": 1
  }, {
    "0": 1448841600000,
    "1": 0
  }, {
    "0": 1449446400000,
    "1": 3
  }, {
    "0": 1450051200000,
    "1": 1
  }, {
    "0": 1450656000000,
    "1": 0
  }, {
    "0": 1451260800000,
    "1": 0
  }, {
    "0": 1451865600000,
    "1": 0
  }, {
    "0": 1452470400000,
    "1": 0
  }, {
    "0": 1453075200000,
    "1": 0
  }, {
    "0": 1453680000000,
    "1": 0
  }, {
    "0": 1454284800000,
    "1": 0
  }],
  "idx": 5
}];

var ticks = [];
for (var i = 0; i < datasets[0].data.length; i++) {
  ticks.push(datasets[0].data[i][0]);
}

var options = {
  "legend": {
    "position": "ne",
    "noColumns": 6
  },
  "yaxis": {
    "min": 0
  },
  "xaxis": {
    "mode": "time",
    "timeformat": "%d %b",
    //    "tickSize": [7, "day"],
    ticks: ticks,
    "min": 1446163200000,
    "max": 1454544000000 // 1454284800000
  },
  "grid": {
    "clickable": true,
    "hoverable": true
  },
  "series": {
    "stack": true,
    "bars": {
      "show": true,
      "barWidth": 181440000.00000003,
      align: 'center'
    }
  }
};

$.plot($('#CAGraph'), datasets, options);


$("#CAGraph").bind("plothover", function(event, pos, item) {
  if (item) {
    var epoch = new Date(item.datapoint[0]);
    var percent = item.datapoint[1] - item.datapoint[2];
    $("#tooltip").html(item.series.label + " " + (percent) + "<br>Total: " + item.datapoint[1]).css({
      top: item.pageY - 25,
      left: item.pageX + 10,
      padding: 5
    }).fadeIn(200);
  } else {
    $("#tooltip").hide();
  }
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="https://rawgit.com/flot/flot/master/jquery.flot.js"></script>
<script src="https://rawgit.com/Codicode/flotanimator/master/jquery.flot.animator.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/flot/0.8.3/jquery.flot.time.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/flot/0.8.3/jquery.flot.stack.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/flot.tooltip/0.8.5/jquery.flot.tooltip.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.11.2/moment.min.js"></script>

<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" integrity="sha256-7s5uDGW3AHqw6xtJmNNtr+OBRJUlgkNJEo78P4b0yRw= sha512-nNo+yCHEyn0smMxSswnf/OnX6/KwJuZTlNZBjauKhTK0c+zT+q5JOCx0UFhXQ6rJR9jg6Es8gPuD2uZcYDLqSw=="
crossorigin="anonymous">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha256-KXn5puMvxCw+dAYznun+drMdG1IFl3agK0p/pqT9KAo= sha512-2e8qq0ETcfWRI4HJBzQiA3UoyFk6tbNyG+qSaIBZLyW9Xf3sWZHN/lxe9fTh1U45DpPf07yj94KsUHHWe4Yk1A==" crossorigin="anonymous"></script>


<div id="choices_CAGraph"></div>
<div id="CAGraph" style="width:910px;height:400px"></div>
<div id=tooltip class="popover" role="tooltip">
  <div class="arrow"></div>
  <h3 class="popover-title"></h3>
  <div class="popover-content"></div>
</div>

好的,任何解决方案都必须考虑 Bootstrap 的弹出窗口功能的异步行为,因为 Raidri 之前在几条评论中正确说明了这一点。此外,它还必须考虑到 plothover 事件将比异步弹出窗口 show/hide 调用完成要快得多的事实。换句话说,您将不得不特别注意系统的状态

这让我明白,作为悬停事件处理程序的一部分,一遍又一遍地创建弹出窗口 object 是一个 no-no。它必须创建一次,然后才显示和隐藏。

我还注意到,在您的最新代码中,您再次忽略了我之前的观点,即标题和内容属性是字符串 函数 return 字符串。您 returning jQuery object 是您的 - 错误。

首先,我创建了一个新的 jQuery 函数。这将帮助我维护闭包中所需的状态,包括弹出窗口 object.

$.fn.popoverTooltip = function (selector, popoverSelector) {
  // the rest of the code forming a nice closure
}
$.plot('#CAGraph', datasets, options);
$("#tooltip").popoverTooltip("#CAGraph", ".popover");

作为封闭代码的一部分,我在顶部创建了一些局部变量:

var barIdShown = null;
var chart = $(selector);
var tooltip = $(this);

var popoverProcessor = function () {
  // mysterious code maintaining state
}();

然后是一个名为 popoverProcessor 的新 object(稍后将显示的代码),它将完成大部分实际工作并保持状态。

在该代码之后,我创建了实际的弹出窗口并绑定了一些事件处理程序。首先:我需要知道 何时 弹出 hide/show 功能 实际上 完成,所以我向相关的 BS 弹出事件添加了处理程序。第二:我将处理程序绑定到 plothover 事件以处理显示或隐藏工具提示。

//create popover
tooltip.popover({
  html: true,
  title : popoverProcessor.getTitle,
  content : popoverProcessor.getContent
});

// bind events to know when shown or hidden
tooltip.on("hidden.bs.popover", popoverProcessor.hideDone);
tooltip.on("shown.bs.popover", popoverProcessor.showDone);

// bind hover event to chart
chart.bind("plothover", function(event, pos, item) {
  var thisBarId;
  if (item) {
    thisBarId = seriesIndex * 10000 + dataIndex;
    if (thisBarId !== barIdShown) {
      if (barIdShown) {
        popoverProcessor.hide();
      }
      popoverProcessor.setItem(item);
      popoverProcessor.show();
      barIdShown = thisBarId;
    }
  }
  else {
    if (barIdShown) {
      popoverProcessor.hide();
      barIdShown = null;
    }
  }
});

首先请注意,我在 popoverProcessor 到 return 工具提示的标题和内容中使用了函数。然后为了知道光标是否悬停在另一个条形段上而不移出条形,我创建了一个特殊的 "bar identifier"。 (如果它发生变化,我会在 re-showing 之前隐藏弹出窗口。)注意,在这个处理程序中 "synchronous" 一切都很好;异步部分在这个神秘的 popoverProcessor object.

中处理
var popoverProcessor = function () {
  var item = null;
  var state = "hidden";
  var taskQueue = [];
  var showPopover = function () {
    tooltip.popover("show");
    $(popoverSelector).css({
      top : item.pageY,
      left : item.pageX + 10
    });
    $(".popover.right > .arrow").css({
      top : "20%",
    });      
    state = "showing";
  };
  var hidePopover = function () {
    tooltip.popover("hide");
    state = "hiding";
  };
  var processNextTask = function () {
    var task;
    if (taskQueue.length > 0) {
      task = taskQueue.shift();
      if (task === "show") {
        showPopover();
      }
      else {
        hidePopover();
      }      
    }
  };

  return {
    setItem: function (newItem) { item = newItem; },
    getTitle: function () { 
      if (item) {
        return item.series.label; 
      }
      return "unknown item";
    },
    getContent: function () { 
      var percent;
      if (item) {
        percent = item.datapoint[1].toFixed(0);
        return percent.toString() + "<br />Total: " + item.datapoint[1];
      }
      return "unknown item";
    },
    hideDone: function () {
      state = "hidden";
      processNextTask();
    },
    showDone: function () {
      state = "shown";
      processNextTask();
    },
    hide: function () {
      if (state === "shown") {
        hidePopover();
      }
      else {
        taskQueue.push("hide");
      }
    },
    show: function () {
      if (state === "hidden") {
        showPopover();
      }
      else {
        taskQueue.push("show");
      }
    }
  };
}();

publicobject有一套方法。您可以设置正在处理的项目,您可以获取弹出窗口的标题和内容,您可以发出弹出窗口已显示(或已隐藏)的信号,您可以请求显示或隐藏弹出窗口。

处理器维护弹出框的当前状态object。它们是:"hidden"、"showing"、"shown" 和 "hiding"。如果你调用hide()并且状态是"shown",代码立即调用内部函数hidePopover开始隐藏popover,否则一个项目被添加到任务queue到指示应尽可能隐藏弹出窗口。如果你调用 show().

也会发生类似的事情

有趣的事情发生在事件处理程序 showDone()hideDone() 中。这是从任务 queue 弹出并处理下一个任务的地方。使用此任务 queue,我在 Bootstrap 异步环境中维护 hide/show 调用的顺序,确保仅在前一个完成时才启动新的显示状态更改。

另请注意,当调用 .popover("show") 时,工具提示的标题和内容实际上是通过提供的函数计算的。

毫无疑问,可以重构此代码以使其更简单,但我已经完成了。