与背景对应的 SVG 文本颜色

SVG text color with correspond to background

我正在使用 D3.js 制作如下图所示的图表:

一般来说,一切正常,但我不知道如何在栏没有覆盖所有标签时让标签可见。我的第一个想法是添加两个标签。一个带有白色,另一个带有条形颜色。我希望能发生一些神奇的事情,栏外的文字会变成绿色,但这是行不通的。

这是我用来添加标签的代码片段:

var rect = this.svg.selectAll("text")
                .data(dataset, dataset.key)
                .enter();

rect.append("text")
  .text(function(d) { return d.value; })
  .attr("text-anchor", "end")
  .attr("x", w-10) 
  .attr("y", function(d, i) { return graph.xScale(i) + graph.xScale(1)/2; })
  .attr("fill", color)
  .attr("class", "value");

rect.append("text")
  .text(function(d) { return d.key; })
  .attr("text-anchor", "start")
  .attr("x", 10) 
  .attr("y", function(d, i) { return graph.xScale(i) + graph.xScale(1)/2; })
  .attr("fill", color)
  .attr("class", "key");

rect.append("text")
  .text(function(d) { return d.key; })
  .attr("text-anchor", "start")
  .attr("x", 10) 
  .attr("y", function(d, i) { return graph.xScale(i) + graph.xScale(1)/2; })
  .attr("fill", textColor)
  .attr("class", "keybg");

如何实现这样的效果?

这可以通过剪贴蒙版轻松完成。这是一个静态 SVG 来说明这一点:

<svg width="400" height="200" viewBox="0 0 400 200">
  <defs>
    <clipPath id="clip_1">
      <rect width="50" height="38" />
    </clipPath>
    <clipPath id="clip_2">
      <rect width="100" height="38" />
    </clipPath>
    <clipPath id="clip_3">
      <rect width="150" height="38" />
    </clipPath>
    <clipPath id="clip_4">
      <rect width="250" height="38" />
    </clipPath>
    <clipPath id="clip_5">
      <rect width="300" height="38" />
    </clipPath>
  </defs>
  <rect width="400" height="200" fill="white" path="none" />
  <g transform="translate(0,1)">
    <text x="10" y="28" font-family="Verdana" font-size="20" fill="red">Lorem ipsum dolor sit amet</text>
    <g clip-path="url(#clip_1)">
      <rect width="50" height="38" fill="red" />
      <text x="10" y="28" font-family="Verdana" font-size="20" fill="white">Lorem ipsum dolor sit amet</text>
    </g>
  </g>
  <g transform="translate(0,41)">
    <text x="10" y="28" font-family="Verdana" font-size="20" fill="orange">Consectetur adipiscing elit</text>
    <g clip-path="url(#clip_2)">
      <rect width="100" height="38" fill="orange" />
      <text x="10" y="28" font-family="Verdana" font-size="20" fill="white">Consectetur adipiscing elit</text>
    </g>
  </g>
  <g transform="translate(0,81)">
    <text x="10" y="28" font-family="Verdana" font-size="20" fill="green">Proin egestas suscipit justo</text>
    <g clip-path="url(#clip_3)">
      <rect width="150" height="38" fill="green" />
      <text x="10" y="28" font-family="Verdana" font-size="20" fill="white">Proin egestas suscipit justo</text>
    </g>
  </g>
  <g transform="translate(0,121)">
    <text x="10" y="28" font-family="Verdana" font-size="20" fill="blue">Nam eget magna gravida eros</text>
    <g clip-path="url(#clip_4)">
      <rect width="250" height="38" fill="blue" />
      <text x="10" y="28" font-family="Verdana" font-size="20" fill="white">Nam eget magna gravida eros</text>
    </g>
  </g>
  <g transform="translate(0,161)">
    <text x="10" y="28" font-family="Verdana" font-size="20" fill="purple">Accumsan tempor eget sed augue</text>
    <g clip-path="url(#clip_5)">
      <rect width="300" height="38" fill="purple" />
      <text x="10" y="28" font-family="Verdana" font-size="20" fill="white">Accumsan tempor eget sed augue</text>
    </g>
  </g>
</svg>

基本上您需要做的是用两种不同的颜色绘制每个文本段,并使用剪贴蒙版显示背景上的文本。例如,本例中的第一个柱创建如下:

1:定义与前景物体形状完全匹配的裁剪路径:

<defs>
  <clipPath id="clip_1">
    <rect width="50" height="38" />
  </clipPath>
  <!-- more paths here -->
</defs>

2: 将要查看的文字绘制在背景色上:

<text x="10" y="28" font-family="Verdana" font-size="20" fill="red">Lorem ipsum dolor sit amet</text>

3: 使用与剪辑路径和背景文本相同的坐标创建一个包含前景对象和文本的组,并包含一个 clip-path 参数以裁剪超出前景对象的文本:

<g clip-path="url(#clip_1)">
  <rect width="50" height="38" fill="red" />
  <text x="10" y="28" font-family="Verdana" font-size="20" fill="white">Lorem ipsum dolor sit amet</text>
</g>

将它集成到您​​的 D3 代码中应该不会太难。

娇气的 ossifrage 的 已经描述了使用剪辑路径的方法。我用 d3 方式整理了一个工作片段:

var svg = d3.select("body")
    .append("svg")
    .attr({
        width: 400,
        height: 400
    });

var textOut = svg.append("text")
    .attr({
        x: 120,
        y: 66
    })
    .style({
        fill: "black",
        stroke: "none"
    })
    .text("Description");

var rect = svg.append("rect")
                    .attr({
                        id: "rect",
                        x: 50,
                        y: 50,
                        width: 100,
                        height: 20
                    })
                    .style({
                        fill: "limegreen",
                        stroke: "darkgreen"
                    });

svg.append("clipPath")
    .attr("id", "clip")
    .append("use")
    .attr("xlink:href", "#rect");

var textIn = svg.append("text")
    .attr({
        x: 120,
        y: 66
    })
    .style({
        fill: "white",
        stroke: "none",
        "clip-path": "url(#clip)"
    })
    .text("Description");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

我通过不在 defs 部分设置 clipPath 而是链接到已经通过 xlink:href 绘制的 rect 来进一步缩短内容:

svg.append("clipPath")
    .attr("id", "clip")
    .append("use")
    .attr("xlink:href", "#rect");

这将产生如下所示的 svg 结构:

  <text x="120" y="66" style="fill: rgb(0, 0, 0); stroke: none;">Description</text>
  <rect id="rect" x="50" y="50" width="100" height="20" style="fill: rgb(50, 205, 50); stroke: rgb(0, 100, 0);"/>
  <clipPath id="clip">
    <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#rect"/>
  </clipPath>
  <text x="120" y="66" style="fill: rgb(255, 255, 255); stroke: none; clip-path: url(#clip);">Description</text>

实际上,仅使用 CSS 即可完成此操作。有一个 CSS 属性 名为 mix-blend-mode

...describes how an element's content should blend with the content of the element's direct parent and the element's background.

因此,只需在 CSS:

中进行设置即可
yourSelector {
    mix-blend-mode: someValue;
}

这是一个演示:

text {
 mix-blend-mode: difference;
}
<svg width="400" heigth="200">
 <rect x="10" y="10" height="30" width="350" fill="green"></rect>
 <rect x="10" y="50" height="30" width="150" fill="green"></rect>
 <rect x="10" y="90" height="30" width="50" fill="green"></rect>
 <text x="20" y="30" fill="white">I am a very very very very long long long long long text</text>
 <text x="20" y="70" fill="white">I am a very very very very long long long long long text</text>
 <text x="20" y="110" fill="white">I am a very very very very long long long long long text</text>
</svg>

有两个问题:无法精确控制颜色,IE/Edge不支持。