user3495481 user3495481 - 1 month ago 5
CSS Question

SVG text color with correspond to background

I am using D3.js to make a graph like on this image:

enter image description here

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 was to add two labels. One with white color and another with bar color. I hoped that some magic could happen and text outside of the bar would be green, which did not work.

Here is the piece of code that i use to add labels:

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");


How to achieve such effect?

Answer

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