6

I have the following simple example, When the line extends outside the rectangle, I want to clip it. I already have the rectangle used as an outline, what is a simple way to the same rectangle as a clipping path? My current approach using id is ignored. This related question has an answer but it requires creating the clip area separately. I'd like to re-use my info rather than repeat nearly the same info.

<!DOCTYPE html>
<meta charset="utf-8">

<body>
<script src = "http://d3js.org/d3.v3.min.js"> </script>
<script>

var margin = {top: 100, right: 20, bottom: 20, left: 20},
    width = 600 - margin.left - margin.right,
    height = 270 - margin.top - margin.bottom;

var xdata = d3.range(0, 20);
var ydata = [1, 4, 5, 9, 10, 14, 15, 15, 11, 10, 5, 5, 4, 8, 7, 5, 5, 5, 8, 10];


var xy = []; // start empty, add each element one at a time
for(var i = 0; i < xdata.length; i++ ) {
   xy.push({x: xdata[i], y: ydata[i]});
}

var xscl = d3.scale.linear()
    .domain(d3.extent(xy, function(d) {return d.x;})) //use just the x part
    .range([margin.left, width + margin.left])

var yscl = d3.scale.linear()
    .domain([1, 8]) // use just the y part
    .range([height + margin.top, margin.top])

var slice = d3.svg.line()
  .x(function(d) { return xscl(d.x);}) // apply the x scale to the x data
  .y(function(d) { return yscl(d.y);}) // apply the y scale to the y data

var svg = d3.select("body")
    .append("svg")

svg.append('rect') // outline for reference
    .attr({x: margin.left, y: margin.top,
           width: width,
           height: height,
           id: "xSliceBox",
           stroke: 'black',
           'stroke-width': 0.5,
           fill:'white'});

svg.append("path")
    .attr("class", "line")
    .attr("d", slice(xy))
    .attr("clip-path", "#xSliceBox")
    .style("fill", "none")
    .style("stroke", "red")
    .style("stroke-width", 2);

</script>
</body>
Bryan Hanson
  • 6,055
  • 4
  • 41
  • 78

1 Answers1

8

You can't reference the rectangle directly in the clip-path property, you need to create a <clipPath> element. Then, inside the <clipPath> element, you can use a <use> element to reference the rectangle.

(Yes, it's round-about and more complicated that you would think it should be, but that's how the SVG specs defined it.)

Working from your code:

var svg = d3.select("body")
    .append("svg")

var clip = svg.append("defs").append("clipPath")
   .attr("id", "clipBox");

svg.append('rect') // outline for reference
    .attr({x: margin.left, y: margin.top,
           width: width,
           height: height,
           id: "xSliceBox",
           stroke: 'black',
           'stroke-width': 0.5,
           fill:'white'});

clip.append("use").attr("xlink:href", "#xSliceBox");

svg.append("path")
    .attr("class", "line")
    .attr("d", slice(xy))
    .attr("clip-path", "url(#clipBox)") //CORRECTION
    .style("fill", "none")
    .style("stroke", "red")
    .style("stroke-width", 2);

You could also do this the other way around, defining the rectangle within the clipPath element and then using a <use> element to actually draw it to the screen. Either way, you want to only define the rectangle once, so that if you decide to change it you only have to do it in one place and the other will update to match.

AmeliaBR
  • 27,344
  • 6
  • 86
  • 119
  • Thanks for your really clear and concise explanation. When I try it however, there is no clipping. There is no error thrown, and when I look at the page elements, everything seems to be defined properly. Perhaps there is something still needed? – Bryan Hanson May 16 '14 at 12:30
  • I must be missing something fundamental, as I hard-wired the `clipPath` and it still doesn't work. Do I need to point to some definition of `xlink`? I've seen this on other answers. – Bryan Hanson May 17 '14 at 23:24
  • 1
    Sorry I didn't catch your comment from a few days ago. If your svg was stand-alone, you would need to declare the xlink namespace, but if it's embedded in an html5 document you can skip that (the html5 parser just ignores namespaces anyway). The problem was in the way the `clip-path` attribute was defined -- although we're adding `clip-path` as a presentation attribute, it could equally have been added as a style attribute, and like all other style properties it uses the CSS `url(...)` syntax. – AmeliaBR May 18 '14 at 15:08
  • 1
    General rule in SVG: if the attribute has an xlink prefix, you can just use the bare fragment, like `.attr("xlink:href", "#xSliceBox");`. When you're referencing a file fragment for other properties, you need to wrap it in the url function, like `.attr("clip-path", "url(#clipBox)")`. Other properties where you use the functional notation are filters, fill or stroke (when referencing a gradient or pattern), or line markers. All of these properties can also be declared in the CSS, so they use the CSS format. Again, sorry I didn't catch the error earlier. The code above has been corrected. – AmeliaBR May 18 '14 at 15:15
  • Thanks, and thanks for the background. I had actually tried that after some hunting around. Something strange is going on, it is clipped but only a tiny piece of the line shows. I've put it in [fiddle](http://jsfiddle.net/Ewb8X/) if you have a chance to take a look. I just don't see what the problem is. – Bryan Hanson May 18 '14 at 23:17
  • 1
    The problem is that the clip-path is being applied *before* the translation. (Or technically, it's being applied as if the rectangle was drawn in the path's new coordinate system; if you remove the transform attribute, you'll see that the tiny piece of line that doesn't get clipped is the only part within the rectangle.) I'm not sure what you changed in your code to require that extra translation, but a simple solution is to put the path inside a group and put the clip-path attribute on the ``; that way, the path will be clipped after being moved into place. http://jsfiddle.net/Ewb8X/1/ – AmeliaBR May 19 '14 at 03:06
  • Ah geez... I see what you mean. I had toyed with the order of operations but that is not the same as grouping. Thank you so much, you've been super helpful and now I can put this to use. – Bryan Hanson May 19 '14 at 11:45