block by emeeks a1e48992be56681e3f93

d3.svg.ribbon example

Full Screen

An example of d3.svg.ribbon.

Drag the circles to see the underlying single svg:path element re-interpolated based on the changing position of the points and their radii that are used to generate it. This is a different way of generating areas than d3.svg.area and might prove more suitable for some applications.

index.html

<html>
<head>
  <title>d3.svg.ribbon Example</title>
  <meta charset="utf-8" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.16/d3.min.js" charset="utf-8" type="text/javascript"></script>
<script src="d3.svg.ribbon.js" type="text/JavaScript"></script>
</head>
<style>
  svg {
    height: 1000px;
    width: 1000px;
  }

</style>
<body>

<div id="viz">
  <svg>
  </svg>
</div>
</body>
  <footer>

<script>

var points = [{x: 20, y:20, t: 10},{x:200,y:20, t: 5},{x:20,y:200, t: 15},{x:200,y:200, t: 2}];

ribbon = d3.svg.ribbon()
  .x(function(d) {return d.x})
  .y(function(d) {return d.y})
  .r(function(d) {return d.t});

drag = d3.behavior.drag().on("drag", function (d) {
  d.x = d3.event.x;
  d.y = d3.event.y;
  redraw();
})

d3.select("svg")
.append("path")
.style("fill", "lightblue")
.style("stroke", "blue")
.style("stroke-opacity", 0.75)
.style("stroke-width", "2px")
.attr("d", ribbon(points))

d3.select("svg")
.selectAll("circle")
.data(points)
.enter()
.append("circle")
.attr("r", 3)
.style("fill", "lightgreen")
.style("stroke", "blue")
.style("stroke-width", "2px")
.attr("cx", function (d) {return d.x})
.attr("cy", function (d) {return d.y})
.attr("r", function (d) {return d.t})
.style("stroke-opacity", 0.75)
.call(drag);

function redraw() {
  d3.selectAll("circle")
    .attr("cx", function (d) {return d.x})
    .attr("cy", function (d) {return d.y});

  d3.select("path")
    .attr("d", ribbon(points));
}

</script>
  </footer>

</html>

d3.svg.ribbon.js

d3.svg.ribbon = function() {
	var _lineConstructor = d3.svg.line();
	var _xAccessor = function (d) {return d.x}
	var _yAccessor = function (d) {return d.y}
	var _rAccessor = function (d) {return d.r}
	var _interpolator = "linear-closed";

	function _ribbon(pathData) {

		var bothPoints = buildRibbon(pathData);

		return _lineConstructor.x(_xAccessor).y(_yAccessor).interpolate(_interpolator)(bothPoints);
	}

	_ribbon.x = function (_value) {
		if (!arguments.length) return _xAccessor;

		_xAccessor = _value;
		return _ribbon;
	}

	_ribbon.y = function (_value) {
		if (!arguments.length) return _yAccessor;

		_yAccessor = _value;
		return _ribbon;
	}

	_ribbon.r = function (_value) {
		if (!arguments.length) return _rAccessor;

		_rAccessor = _value;
		return _ribbon;
	}

	_ribbon.interpolate = function(_value) {
		if (!arguments.length) return _interpolator;

		_interpolator = _value;
		return _ribbon;
	}

return _ribbon;

function offsetEdge(d) {
  var diffX = _yAccessor(d.target) - _yAccessor(d.source);
  var diffY = _xAccessor(d.target) - _xAccessor(d.source);

  var angle0 = ( Math.atan2( diffY, diffX ) + ( Math.PI / 2 ) );
  var angle1 = angle0 + ( Math.PI * 0.5 );
  var angle2 = angle0 + ( Math.PI * 0.5 );

  var x1 = _xAccessor(d.source) + (_rAccessor(d.source) * Math.cos(angle1));
  var y1 = _yAccessor(d.source) - (_rAccessor(d.source) * Math.sin(angle1));
  var x2 = _xAccessor(d.target) + (_rAccessor(d.target) * Math.cos(angle2));
  var y2 = _yAccessor(d.target) - (_rAccessor(d.target) * Math.sin(angle2));

  return {x1: x1, y1: y1, x2: x2, y2: y2}
}

function buildRibbon(points) {
  var bothCode = [];
  var x = 0;
  var transformedPoints = {};

  while (x < points.length) {
    if (x !== points.length - 1) {
      transformedPoints = offsetEdge({source: points[x], target: points[x + 1]});
      var p1 = {x: transformedPoints.x1, y: transformedPoints.y1};
      var p2 = {x: transformedPoints.x2, y: transformedPoints.y2};
      bothCode.push(p1,p2);
      if (bothCode.length > 3) {
        var l = bothCode.length - 1;
        var lineA = {a: bothCode[l - 3], b: bothCode[l - 2]};
        var lineB = {a: bothCode[l - 1], b: bothCode[l]};
        var intersect = findIntersect(lineA.a.x, lineA.a.y, lineA.b.x, lineA.b.y, lineB.a.x, lineB.a.y, lineB.b.x, lineB.b.y);
        if (intersect.found == true) {
          lineA.b.x = intersect.x;
          lineA.b.y = intersect.y;
          lineB.a.x = intersect.x;
          lineB.a.y = intersect.y;
        }
      }
    }

    x++;
  }
  x--;
  //Back
  while (x >= 0) {
    if (x !== 0) {
      transformedPoints = offsetEdge({source: points[x], target: points[x - 1]});
      var p1 = {x: transformedPoints.x1, y: transformedPoints.y1};
      var p2 = {x: transformedPoints.x2, y: transformedPoints.y2};
      bothCode.push(p1,p2);
      if (bothCode.length > 3) {
        var l = bothCode.length - 1;
        var lineA = {a: bothCode[l - 3], b: bothCode[l - 2]};
        var lineB = {a: bothCode[l - 1], b: bothCode[l]};
        var intersect = findIntersect(lineA.a.x, lineA.a.y, lineA.b.x, lineA.b.y, lineB.a.x, lineB.a.y, lineB.b.x, lineB.b.y);
        if (intersect.found == true) {
          lineA.b.x = intersect.x;
          lineA.b.y = intersect.y;
          lineB.a.x = intersect.x;
          lineB.a.y = intersect.y;
        }
      }
    }

    x--;
  }

  return bothCode;
}

function findIntersect(l1x1, l1y1, l1x2, l1y2, l2x1, l2y1, l2x2, l2y2) {
    var d, a, b, n1, n2, result = {
        x: null,
        y: null,
        found: false
    };
    d = ((l2y2 - l2y1) * (l1x2 - l1x1)) - ((l2x2 - l2x1) * (l1y2 - l1y1));
    if (d == 0) {
        return result;
    }
    a = l1y1 - l2y1;
    b = l1x1 - l2x1;
    n1 = ((l2x2 - l2x1) * a) - ((l2y2 - l2y1) * b);
    n2 = ((l1x2 - l1x1) * a) - ((l1y2 - l1y1) * b);
    a = n1 / d;
    b = n2 / d;

    result.x = l1x1 + (a * (l1x2 - l1x1));
    result.y = l1y1 + (a * (l1y2 - l1y1));

    if ((a > 0 && a < 1) && (b > 0 && b < 1)) {
        result.found = true;
    }

    return result;
};

}