【问题标题】:Dynamically Adding a tooltip to static SVG Using D3使用 D3 向静态 SVG 动态添加工具提示
【发布时间】:2017-05-19 03:04:30
【问题描述】:

我们有一个静态 SVG 图像,我们正在尝试动态添加工具提示,例如在 AngularJS 应用程序的上下文中使用 d3.js 在 SVG 图像内的对象上的悬停事件。

SVG 图像是一个平面图并且相当复杂,但是我们在 POC 过程中的起步非常小。下面是一个小节的代表性sn-p:

<g id="f3s362c12">
  <g>
    <rect x="75.2" y="92.4" pointer-events="visible" fill="none" 
    width="64.7" height="57.8" />
    <polyline fill="none" stroke="#CDDDED" stroke-width="0.5" 
     stroke-miterlimit="10" points="118.4,149.9 140.3,149.9 140.3,92.4
     75.2,92.4 75.2,128.7" />
  </g>
  <g>
    <text transform="matrix(1 0 0 1 87.8719 144.8836)" fill="#010101" 
     font-family="arial, sans-serif" font-size="13.4182">362.12</text>
  </g>
</g>

D3.js 对我们来说是新的,但是从我们的研究来看,它似乎能够做我们需要它做的事情,因为它似乎被设计为与 SVG 一起使用并在 SVG 中表示数据,但是所有的我们发现的例子是动态创建 SVG(主要是图表)但不操作现有的 SVG 图像。

简而言之,我们需要做的是:

  1. 查找 id 以“f3”开头的 g 标签,如上面的 g id="f3s362c12"
  2. 对于与 g 标签关联的每个矩形,添加一个工具提示悬停事件(可能是标签?)
  3. 当用户将鼠标悬停在说 g id="f3s362c12" 上时,选择矩形并获取我从 .csv 文件 IE 加载的 f3s362c12 的相应数据记录:

({"floor":"3","location":"f3s362c12","name":"David Byrne","occ":"歌手","img":"img/davidAvatar.jpg\r"})

  1. 将此信息添加到工具提示/标签中,这样当您将鼠标悬停在 g id=f3s362c12 上时,您会看到 David Byrne 的工具提示,他的职业是歌手及其头像。

我创建了一个Plunk

  1. 在 HTML 中加载 SVG
  2. 加载 .csv 文件。

我们遇到的问题是 d3.js。 例如,在我们的Plunk 中,在 script.js 中,我们执行以下操作来查找我们的 g 标签:

 var svg = d3.select("#svgFP");
 var allG = svg.selectAll("g").each(function (d,i) {}

然而,在这一点上我们碰壁了,因为我们试图使用“this”关键字在 allG 上找到一个矩形。

  if (this.id.indexOf("f3") > -1)
    {
        //1. Add label/div/hover
        //2. Find corresponding record from array object.
        //3. Inject respective name, occupation and image into label/div along with mouseover/mouseout event.
    }

我们一直在使用 Firebug 来尝试查找要使用的属性,但至少可以说非常令人沮丧且毫无结果,所以我们认为 SO 中可能有一两个 d3/angular 大师可能能够给我们指路。

提前致谢。

【问题讨论】:

  • 1. “不确定为什么当 SVG 设置为 INCLUDE 时 SELECT 不起作用,但它不起作用”——因为控制器在 ng-include 解析之前运行。 2.“出现错误:$digest 已经在进行中”您不需要任何应用程序或 $http 回调中的任何内容。只要 scope.data = response 就可以了。 3. 你的 1、2、3 计划很好 - 你有没有尝试过?或者你想让别人为你做这件事……
  • 感谢您的建议。我们目前的停止点似乎是我们尝试类似的地方:this.select("rect");并接收:TypeError:this.select 不是函数谢谢。
  • 我取得了一些进展,并且能够解决有关包含 问题的问题。我们仍然停留在如何使用 D3 使用此示例为我们的解决方案正确实现工具提示:bl.ocks.org/biovisualize/1016860 我们为更新创建了一个新的 Plunk:plnkr.co/edit/WYeDdp1ulfJRKAlY2gRz?p=preview 悬停工具栏正在显示数据,但是我们没有添加悬停事件到正确的 并且悬停事件仅发生在背景矩形上的内容上。
  • g[id^='f3'] 应该比在 javascript 中迭代更有效地找到匹配项
  • @RobertLongson 正是我在解决方案中使用的。

标签: javascript angularjs d3.js svg


【解决方案1】:

您在 plunker 中遇到的第一个问题是您的 CSV:CSV 的第一行必须是 标题,它定义了每一列是什么。因此,我将您的 CSV 修改为:

floor,location,name,surname,role,image
3,f3s362c12,David ,Byrne,Singer,img/davidAvatar.jpg
3,f3s362c11,Tina,Weymouth,Bassist,img/tinaAvatar.jpg
3,f3s362c2,Jerry,Harrison,Keyboards,img/jerryAvatar.jpg
3,f3s362c1,Chris,Frantz,Drums,img/chrisAvatar.jpg

现在是你的实际问题。

D3 代码的最佳解决方案是将数据绑定到 DOM 元素(在本例中是您的 SVG)。但是,由于您已经有一个静态 SVG,不是用 D3 制作的,也没有任何绑定数据,所以这里有一个解决方案。

首先,选择所有相关的组元素:

var groups = d3.selectAll("g[id^='f3']");

并将mouseover 代码添加到该选择中。

mouseover 代码中出现了这个解决方案最重要的部分:我们获取光标所在元素的 ID...

var groupId = this.id

...然后,根据这个ID,我们过滤之前加载的CSV:

var thisData = data.filter(function(d) {
    return d.location === groupId
});

这就是上面代码的作用:我们将 CSV 加载到名为 data 的变量中。该变量是一个对象数组,每个对象都有一个 location 属性(查看我创建的 CSV 标头)。然后,我们将每个对象的location (d.location) 与组的 ID (groupId) 进行比较。

现在,您有一个名为 thisData 的变量,您可以使用它来填充您的工具提示。

这是一个带有 MCVE 版本代码的演示:

var tooltip = d3.select("body")
            .append("div")
            .style("position", "absolute")
            .style("background-color", "gainsboro")
            .style("border", "1px solid black")
            .style("padding", "20px")
            .style("pointer-events", "none")
            .style("font-size", "12px");

        var data = d3.csvParse(d3.select("#csv").text());

        var groups = d3.selectAll("g[id^='f3']");

        groups.on("mousemove", function() {
            var groupId = this.id
            var thisData = data.filter(function(d) {
                return d.location === groupId
            });
            tooltip.html("Name: " + thisData[0].name + " " + thisData[0].surname + "<br>Role: " + thisData[0].role)
                .style("top", (d3.event.pageY - 2) + "px").style("left", (d3.event.pageX + 2) + "px")
                .style("visibility", "visible");
        }).on("mouseout", function() {
            tooltip.style("visibility", "hidden");
        });
pre {
	display: none;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300" height="100%">
    <g id="background">
        <rect x="1.5" y="0.3" fill="#5A8CC9" width="298.7" height="300.4" />
    </g>
    <g id="f3s362c12">
        <g>
            <rect x="75.2" y="92.4" pointer-events="visible" fill="none" width="64.7" height="57.8" />
            <polyline fill="none" stroke="#CDDDED" stroke-width="0.5" stroke-miterlimit="10" points="118.4,149.9 140.3,149.9 140.3,92.4
                75.2,92.4 75.2,128.7        " />
        </g>
        <g>
            <text transform="matrix(1 0 0 1 87.8719 144.8836)" fill="#010101" font-family="arial, sans-serif" font-size="13.4182">362.12</text>
        </g>
    </g>
    <g id="f3s362c11">
        <g>
            <rect x="75.2" y="149.9" pointer-events="visible" fill="none" width="64.7" height="57.8" />
            <polyline fill="none" stroke="#CDDDED" stroke-width="0.5" stroke-miterlimit="10" points="118.4,207.8 140.3,207.8 140.3,149.9
                75.2,149.9 75.2,186.2       " />
        </g>
        <g>
            <text transform="matrix(1 0 0 1 87.8719 201.6532)" fill="#010101" font-family="arial, sans-serif" font-size="13.4182">362.11</text>
        </g>
    </g>
    <g id="f3s362c2">
        <g>
            <rect x="140.3" y="149.9" pointer-events="visible" fill="none" width="68.8" height="57.8" />
            <polyline fill="none" stroke="#CDDDED" stroke-width="0.5" stroke-miterlimit="10" points="208.7,183.5 208.7,149.9 140.3,149.9
                140.3,207.8 185.8,207.8         " />
        </g>
        <g>
            <text transform="matrix(1 0 0 1 163.782 201.6532)" fill="#010101" font-family="arial, sans-serif" font-size="13.4182">362.2</text>
        </g>
    </g>
    <g id="f3s362c1">
        <g>
            <rect x="140.3" y="92.4" pointer-events="visible" fill="none" width="68.8" height="57.8" />
            <polyline fill="none" stroke="#CDDDED" stroke-width="0.5" stroke-miterlimit="10" points="208.7,126 208.7,92.4 140.3,92.4
                140.3,149.9 185.8,149.9         " />
        </g>
        <g>
            <text transform="matrix(1 0 0 1 163.782 144.8836)" fill="#010101" font-family="arial, sans-serif" font-size="13.4182">362.1</text>
        </g>
    </g>
</svg>
<pre id="csv">floor,location,name,surname,role,image
3,f3s362c12,David ,Byrne,Singer,img/davidAvatar.jpg
3,f3s362c11,Tina,Weymouth,Bassist,img/tinaAvatar.jpg
3,f3s362c2,Jerry,Harrison,Keyboards,img/jerryAvatar.jpg
3,f3s362c1,Chris,Frantz,Drums,img/chrisAvatar.jpg</pre>

总结一下,步骤如下:

  1. 创建一个选择以获取您的鼠标事件;
  2. 加载并解析您的 CSV;
  3. 当用户将鼠标悬停在&lt;g&gt; 上时,根据该&lt;g&gt; 的ID 过滤您的数据数组;
  4. 使用过滤后的数组创建工具提示的html

PS:在这个 sn-p 中,我使用 &lt;pre&gt; 元素来存储您的 CSV。我这样做只是因为,与 Plunker 不同,我无法使用 S.O. 加载 CSV。 sn-p。

【讨论】:

  • 非常感谢!你在你的回答中提到了这一点,当我努力寻找答案(并且惨遭失败)时,我感到这种挥之不去的怀疑是 D3 更适合绑定到 SVG 的数据,而我正朝着错误的方向前进,但是因为我们'正在研究具有大部分静态楼层布局的平面图,问题是人员不断变化,所以我认为计划是将 SVG 和数据分开。另外,我不知道您是否对开发环境或有关D3的书籍有任何建议。再次感谢!
  • 您可以将 SVG 和数据分开。只要你有一个匹配某个 SVG 属性(id、class 等)的属性值,你就可以对其进行过滤并显示过滤后的信息。
  • 由于我在 AngularJS 控制器的上下文中使用此解决方案,您对将悬停事件推送到 Angular 指令中的最佳方式有什么建议吗?
  • 抱歉,我不是 Angular 用户。
猜你喜欢
  • 2012-12-21
  • 1970-01-01
  • 1970-01-01
  • 2014-05-14
  • 1970-01-01
  • 1970-01-01
  • 2023-03-25
  • 1970-01-01
  • 2019-03-08
相关资源
最近更新 更多