Skip to content Skip to sidebar Skip to footer

SVG Text Color With Correspond To Background

I am using D3.js to make a graph like on this image: Generally, all works fine, but I don't know how to make the labels visible when the bar doesn't cover all of it. My first idea

Solution 1:

The approach to use clip paths has already been described by squeamish ossifrage's answer. I have put together a working snippet doing it the d3 way:

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>

I've further shortened things by not setting up clipPaths in the defs section but instead linking to the rect which has already been drawn via xlink:href:

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

This will result in an svg structure like the following:

  <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>

Solution 2:

This can be done quite easily with clipping masks. Here's a static SVG to illustrate the point:

<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>

Basically what you need to do is draw every text segment in two different colours, and use clipping masks to reveal the text that sits on the background. So for example, the first bar in this example is created as follows:

1: Define a clip path that exactly matches the shape of the foreground object:

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

2: Draw the text to be viewed against the background colour:

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

3: Create a group containing the foreground object and text using the same coordinates as the clip path and background text, and include a clip-path parameter to crop the text where it extends beyond the foreground object:

<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>

It shouldn't be too hard to integrate this into your D3 code.


Solution 3:

Actually, this can be done using CSS only. There is a CSS property named mix-blend-mode, which

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

So, it's just a matter of setting it in the CSS:

yourSelector {
    mix-blend-mode: someValue;
}

This is a demo:

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>

There are two problems: you cannot control the colours precisely, and it is not supported by IE/Edge.


Post a Comment for "SVG Text Color With Correspond To Background"