【问题标题】:How to add a tooltip to existing svg circles?如何向现有的 svg 圈子添加工具提示?
【发布时间】:2019-05-15 13:01:54
【问题描述】:

我有多个从 CorelDraw 导出的现有 SVG 圆圈,并且希望每个圆圈在悬停时在工具提示中显示唯一的文本。 在 g 元素中,我在圆圈中添加了文本元素。我将每个文本定位在相应的圆圈旁边并带有相应的文本。

<g id="cities" class="gradici">
<circle class="first" r="7" />
<circle class="second" r="7 />
</g>

var Citytooltip = svg.selectAll("g.gradici").selectAll("text")
                  .data(naziviGradova)
                  .enter()
                  .append("text")
                  .style("visibility", "hidden")
                  .attr("x", function(d,i){return graddx[i]})
                  .attr("y",function(d,i){return graddy[i]})
                  .text(function(d) {return d;})
                  .attr("font-size", "10px")
                  .attr("fill", "#black");

当我将鼠标悬停在任何圆圈上时,我想得到所有文本在所有圆圈旁边可见/隐藏。

var city= svg.selectAll("#cities circle");

city
   .on("mouseover", (function(){Citytooltip.style("visibility", 
   "visible");}))
   .on("mouseout", (function(){Citytooltip.style("visibility", 
   "hidden");}));

但我正在犹豫如何让文本在我悬停的圆圈上方可见/隐藏。我想我应该以某种方式遍历城市,但我被困在如何做到这一点。有什么想法吗?

【问题讨论】:

    标签: javascript d3.js svg


    【解决方案1】:

    添加titledescriptionmetadata 元素作为circle 元素的内容,以便用户代理提供工具提示(取决于用户代理):

    <g id="cities" class="gradici">
        <desc>A group of circles</desc>
        <circle class="first" r="7">
            <desc>First circle</desc>
        </circle>
        <circle class="second" r="7>
            <desc>Second circle</desc>
        </circle>
    </g>
    

    这是specified by SVG 1.1

    对于现代桌面和移动 Web 浏览器,所提供的元素描述通常按照您的描述和预期呈现——当用户“将指针设备悬停”在 circle 元素上时出现的工具提示,例如包含desc 元素。

    我的建议是不要用复杂而复杂的基于脚本的解决方案重新发明轮子,因为这些解决方案总是会给您的某些用户带来破坏风险,而不是当上述内容已经是 SVG 的一部分并且对您来说足够了。

    【讨论】:

    • 感谢您的回答。我知道我可以为每个圆圈添加标题,但是如果我想添加除文本之外的任何其他内容,使用 javascript 解决这个问题会给我更多的可能性。
    • 您绝对可以在desc 中添加其他元素,所以从技术上讲,您可以添加任何内容,但这些不会在我测试过的浏览器中呈现——Firefox 和 Chrome .也许混合解决方案效果最好——将工具提示内容以声明方式保存在circle 中的desc 元素中,但使用JavaScript(例如mouseenter)和/或CSS 来实际呈现它们。我会写另一个答案来演示这个解决方案。
    • 谢谢,在圆圈内添加 标签对我有用
    【解决方案2】:

    如果用户代理工具提示无法解决问题,则必须自己实现一些功能。我决定仍然依赖声明性的 desc 元素,即使我们自己使用渲染工具提示,我们也可以轻松使用这些元素。

    在下面的 SVG 文档中,工具提示定义用作模板,每当“鼠标”指针(任何可以生成“鼠标*”事件的东西,真的)进入一个元素时,我们都会提取文档片段(@987654322 @) 即其desc 元素的内容,并将这些内容复制到工具提示的“内容”组/图形中。最重要的是,我们计算工具提示应该显示的位置——在鼠标指针的尖端——并且我们调整背景“面板”的大小,使其实际上类似于大多数人接受的工具提示。

    您可以添加自己的样式甚至动画以进一步优化所需的结果。

    更多解释在下面代码中的 cmets 中:

    <?xml version="2.0" encoding="utf-8" ?>
    <!DOCTYPE svg SYSTEM "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
    <svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 100 100">
        <style>
            #rich-tooltip {
                pointer-events: none;
            }
            #rich-tooltip .panel {
                fill: silver;
            }
        </style>
        <defs>
            <!-- the actual template, will be removed from the context and always shown as appended at the end of the document body, so that it is rendered above everything else. -->
            <g id="rich-tooltip">
                <rect class="panel" /><!-- just some decorative background -->
                <g class="contents" /><!-- contents of an element's description will be added as child nodes of this element -->
            </g>
        </defs>
        <circle cx="50" cy="50" r="25" fill="yellow">
            <desc><circle cx="10" cy="10" r="5" /><text dominant-baseline="hanging" fill="red">First circle</text></desc>
        </circle>
        <circle cx="70" cy="50" r="40" fill="green">
            <desc><circle cx="10" cy="10" r="5" /><text dominant-baseline="hanging" fill="red">Second circle</text></desc>
        </circle>
        <script>
            const tooltip = document.getElementById("rich-tooltip");
            tooltip.remove(); /// Initial state of the tooltip is "not shown" (removed from DOM tree)
            var timeout; /// We only show the tooltip once the pointer settles and some time passes
            const tooltip_delay = 1000; /// Delay before showing the tooltip once pointer settles
            var last_tooltip_ev; /// Auxilary variable to be able to calculate movement after showing the tooltip, so we don't remove it immediately but only once the pointer actually moved substantially, this is just a nice touch, not otherwise crucial
            const remove_tooltip_move_threshold = 10; /// How many user units (pixels, normall) the cursor may move before tooltip is hidden
            function on_mouse_move_event(ev) {
                if(document.contains(tooltip)) { /// Is the tooltip being shown?
                    if(last_tooltip_ev) {
                        if(((x, y) => Math.sqrt(x * x + y * y))(ev.clientX - last_tooltip_ev.clientX, ev.clientY - last_tooltip_ev.clientY) >= remove_tooltip_move_threshold) { /// has the pointer moved far enough from where the tooltip was originally shown?
                            tooltip.remove(); /// Hide the tooltip
                        }
                    }
                } else {
                    if(timeout) clearTimeout(timeout);
                    timeout = setTimeout(show_tooltip, tooltip_delay, ev);
                }
            }
            function show_tooltip(ev) {
                const desc = ev.target.querySelector(":scope > desc");
                if(!desc) { /// Does the element that is under the pointer even have a description?
                    tooltip.remove(); /// Hide the tooltip (ignoring the fact it may not be showing in the first place)
                    return;
                }
                document.documentElement.appendChild(tooltip);
                const desc_range = document.createRange();
                desc_range.selectNodeContents(desc); /// Select all children of the description element, as `desc_range`
                const contents = tooltip.querySelector(".contents");
                const contents_range = document.createRange();
                contents_range.selectNodeContents(contents); /// Select all children of the tooltip contents element, as `contents_range`
                contents_range.extractContents(); /// Empty tooltip contents
                contents.appendChild(desc_range.cloneContents()); /// Fill contents with previously selected description. We _copy_ the description -- the original should legitimately stay where it was
                const panel = tooltip.querySelector("rect.panel");
                panel.setAttribute("width", contents.getBBox().width);
                panel.setAttribute("height", contents.getBBox().height); /// "Stretch" the panel so that it covers the tooltip contents
                const pt = document.documentElement.createSVGPoint();
                pt.x = ev.clientX;
                pt.y = ev.clientY;
                const view_pt = pt.matrixTransform(document.documentElement.getScreenCTM().inverse()); /// We need to convert mouse pointer coordinates to the SVG viewbox coordinates
                tooltip.setAttribute("transform", `translate(${view_pt.x} ${view_pt.y})`); /// Move the tooltip to appropriate position
                last_tooltip_ev = ev; /// Save the event to be able to calculate distance later (above)
            }
            addEventListener("mouseover", function(ev) { /// When the pointer gets over an element...
                ev.target.addEventListener("mousemove", on_mouse_move_event); /// We will be tracking pointer movements to trigger timeout for showing the tooltip
                ev.target.addEventListener("mouseout", function() { /// Once the pointer gets anywhere but the element itself -- like over its children or other elements...
                    ev.target.removeEventListener("mouseout", on_mouse_move_event); /// Cancel the whole mousemove business, the behavior will be setup by whatever element the mouse pointer gets over next anyway
                }, { once: true }); /// Once, for this element, everything else will be setup by another call for "mouseover" listener
            });
        </script>
    </svg>
    

    如果没有超时触发等,代码会更简单,但可能是经过深思熟虑且用户友好的工具提示实现将使用延迟并补偿杂散的指针移动,所以我认为保留一些骨架框架是有意义的放置这些内容,并展示如何实施它们。

    这无论如何都是相当理想的,因为您每次只使用一组侦听器——您不需要为要跟踪的每个元素分配侦听器。如果元素有描述,此脚本将确保显示工具提示,仅此而已。暂时,我们确实将mouseout 侦听器分配给一个元素,但通常只有一个侦听器在任何时间点只分配给一个元素——一旦指针离开元素,侦听器就会被删除(并且其他东西会重新分配)它的另一个实例,但这很好)。

    【讨论】:

    • 亲爱的@amn 我不得不承认你的解决方案对我来说太先进了。所以假设我想使用标题选项。我想为每个圈子生成标题标签。 var title = svg.selectAll("g.gradici circle").append("title").如何从数据数组(城市名称以 , 分隔)中获取标题文本以适合相应的标题元素。选项不是手写标题文本,因为我有很多圈子。感谢您的帮助。
    • 如果可以避免的话,我强烈建议你永远不要使用 JavaScript 作为数据源——这就是你的文档的用途——title 元素应该放在 inside你的圈子。 JavaScript 用于使用这些数据,而不是用于生成它们。无论如何,您必须 手写文本——要么是在脚本中(通常但并不总是错误的地方),要么是在标记中。你的标题不会被神奇地生成,是吗?
    • 不幸的是没有魔法 :) 我所有的 svg 文件都是从 CorelDraw 生成的,所以我拥有的任何数据都作为类、id 或某种属性存储在我的 svg 中。所以每个圆圈的工具提示文本应该是每个圆圈的类值。当我将鼠标悬停在圆圈上时,他的工具提示会显示/隐藏。这就是我想要完成的。
    • 我不认为工具提示文本应该class 值——class 属性表示一个类,与任何类型的文本描述几乎没有关系。也许您的意思是您想将class(以及id 等属性的值)值与一些文本相关联?例如,如果您有一个或多个&lt;circle class="gradicii"&gt;...&lt;/circle&gt;,您希望某个文本作为这些的工具提示,特定于具有该类的圆圈的文本?
    • 不,实际上 circle1 有 class="first", circle2 class="second", circle3 class="third"...等等...所以我需要从类值生成数据(第一,第二,第三......)并将它们用于工具提示文本。所以,当我将鼠标悬停在 circle1 上时,工具提示首先显示,当我将鼠标悬停在 circle2 上时,工具提示显示第二个......
    【解决方案3】:

    我想解决我的问题,所以如果有人像我一样陷入困境,我会留下答案。

    var titleCreate = svg.selectAll("g.gradici circle").append("title").text("tooltip");
    for (var i =0; i<naziviGradova.length; i++){                                    
      var textNazivaGradova = naziviGradova[i];
      var title = svg.getElementsByTagName("title");
      title[i].innerHTML = textNazivaGradova;                                    
    

    }

    【讨论】:

      猜你喜欢
      • 2019-03-08
      • 1970-01-01
      • 1970-01-01
      • 2017-12-23
      • 1970-01-01
      • 2017-08-03
      • 2019-03-03
      • 1970-01-01
      相关资源
      最近更新 更多