d3 textwrap合并<b>和<em>标签

时间:2016-05-24 19:06:42

标签: javascript html css d3.js

我正在构建一个图形,我想添加一个注释图层,它既包装文本也包含HTML标记。

例如,我想输入:

vis.append('text')
   .attr("text-anchor", "left")
   .text('left side <b>left side</b> left side left side')
   .call(wrap, 100);

以MBostock的generic text wrapper建模,并且wrap()足够聪明,可以包装此文本,并逐字加粗,甚至可能跨多行。 / p>

换句话说,我希望它能够在必要时进行标记:

左侧左侧 侧面左侧
左侧左侧......

MBostock的文本包装器将其搞砸了,因为:

  1. 它会调用words.reverse(),从而混淆了自然的open-tag / close-tag
  2. 它依赖于<text><tspan>元素,据我所知,这些元素不支持内联<b><em>标记。 (他们确实支持像font-weightfont-style这样的CSS样式,但我不确定如何逐字逐句地进行,只是在tspan的基础上。而且我不愿意使用foreignObject,因为它似乎没有广泛的浏览器支持。)
  3. 我不确定最好的解决方法是什么。有没有人处理过这个问题?

    谢谢,

    亚历

1 个答案:

答案 0 :(得分:4)

好的,我有一个粗略的解决方案。这可能是一种更清洁,更优雅的方式,但总体思路如下:

  1. 保留Mike的<tspan>方法来测量文本的长度,以确定何时断行。
  2. 但是,为中的每个单词添加<tspan> 每行<tspan>更大
  3. 然后,为了设置粗体和斜体的样式,我们会跟踪到目前为止的文本状态(如果<b><em>标记已打开)并使用font-style和{ {1}}相应的CSS属性。
  4. 以下代码执行此操作,并处理多个滞后或单独的font-weight(不是前导<br>):

    <br>

    <强>示例

    运行时:

    function wrap(text, width, block_id) {
      text.each(function() {  
        var text = d3.select(this),
            words = text.text().split(/\s+/).reverse(),
            word,
            lineNumber = 0,
            lineHeight = 1.1, // ems
            y = text.attr("y"),
            x = text.attr("x"),
            dy = 0
            tspan = text.text(null)
                          .append("tspan")
                          .attr("x", x)
                          .attr("y", y)
                          .attr("dy", dy + "em");
    
        word_id_counter = 0
        bold_state = false
        italic_state = false
        while (word = words.pop()) {
          // change state to bold 
          if (word.split('<b>').length > 1){
            bold_state = true
            word = word.replace('<b>','')
          }
          //change state to italic
          if (word.split('<em>').length > 1){
            italic_state = true
            word = word.replace('<em>','')
          }
    
          tspan.append('tspan')
                .attr('id', 'word' + '_' + word_id_counter + '_' + block_id)
                .attr('font-weight', bold_state ? 'bold' : 'normal')
                .attr('font-style', italic_state ? 'italic' : 'normal')
                .text(
                  word.replace('</b>','').replace('</em>','').replace(new RegExp('<br>', 'g'), '')
                  + " "
                );
    
            // handle overflow
          if (tspan.node().getComputedTextLength() >= width) {
            d3.select("#" + 'word' + '_' + word_id_counter + '_' + block_id).remove();
    
            // handle edge case where line break and overflow occur at same time
            word = word.replace('<br>','')
    
            tspan = text.append("tspan")
                          .attr("x", x)
                          .attr("y", y)
                          .attr('id', 'wrap-text')
                          .attr("dy", ++lineNumber * lineHeight + dy + "em")
    
            tspan.append('tspan')
                  .attr('id', 'word' + '_' + word_id_counter + '_' + block_id)
                  .attr('font-weight', bold_state ? 'bold' : 'normal')
                  .attr('font-style', italic_state ? 'italic' : 'normal')
                  .text(word.replace('</em>','').replace('</b>','').replace(new RegExp('<br>', 'g'), '') + " ");
            }
    
          // handle newline (can handle multiple)
          if ((total_br = word.split('<br>').length - 1) > 0){
            lineNumber = lineNumber + total_br
            tspan = text.append("tspan")
              .attr("x", x)
              .attr("y", y)
              .attr('id', 'wrap-text')
              .attr("dy", lineNumber * lineHeight + dy + "em")
          }
    
          //handle close bold: change bold_state back to normal
          if (word.split('</b>').length > 1){
            bold_state = false
          }
    
          //handle close italics: change state back to normal
          if (word.split('</em>').length > 1){
            italic_state = false
          }
    
          word_id_counter = word_id_counter + 1
        }
      });
    }
    

    输出是这样的:

    sample