block by mbostock 6059532

Gradient Bump

Full Screen

A work-in-progress prototype of a bump chart using stroke gradients.

index.html

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

.bump path {
  fill: none;
  stroke: #000;
  stroke-linejoin: round;
}

.bump .halo {
  stroke: #fff;
}

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

var margin = {top: 10, right: 10, bottom: 10, left: 10},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom,
    pairId = 0;

var x = d3.scale.ordinal()
    .domain(["a", "b", "c"])
    .rangeBands([0, width], .5, 0);

var xTangent = 40, // Length of Bézier tangents to control curve.
    yPadding = .3; // Fraction of height for vertical spacing between lines.

var y = d3.scale.linear()
    .range([0, height]);

var z = {
  a: d3.scale.linear().range(["#EEE8B8", "#973333"]).interpolate(d3.interpolateHcl),
  b: d3.scale.linear().range(["#88A391", "#D0D6AF"]).interpolate(d3.interpolateHcl),
  c: d3.scale.linear().range(["#245B7B", "#6A8D94"]).interpolate(d3.interpolateHcl)
};

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

d3.tsv("data.tsv", type, function(error, data) {
  y.domain([0, d3.sum(data, function(d) { return d.weight; }) / (1 - yPadding)]);

  x.domain().forEach(function(k) {
    var n = data.length, y0 = 0, dy = y.domain()[1] * yPadding / (n - 1);
    data.sort(function(a, b) { return a[k] - b[k]; }).forEach(function(d, i) {
      d[k + "-offset"] = y0 + d.weight / 2;
      y0 += d.weight + dy;
    });
    z[k].domain([data[0][k], data[n - 1][k]]);
  });

  data.sort(function(a, b) { return b.weight - a.weight; });

  var bump = svg.append("g")
      .attr("class", "bump")
    .selectAll("g")
      .data(data)
    .enter().append("g");

  bump.append("path")
      .attr("class", "halo")
      .style("stroke-width", function(d) { return y(d.weight) + 2; })
      .attr("d", line);

  x.domain().slice(1).forEach(function(b, i) {
    var a = x.domain()[i];

    var path = bump.append("path")
        .style("stroke-width", function(d) { return y(d.weight); });

    var gradient = bump.append("linearGradient")
        .attr("id", function(d) { return "gradient-" + a + "-" + d.index; })
        .attr("x1", function(d) { return x(a) + x.rangeBand(); })
        .attr("y1", function(d) { return y(d[a + "-offset"]); })
        .attr("x2", function(d) { return x(b); })
        .attr("y2", function(d) { return y(d[b + "-offset"]); })
        .attr("gradientUnits", "userSpaceOnUse")
        .attr("spreadMethod", "pad");

    gradient.append("svg:stop")
        .attr("offset", "0%")
        .attr("stop-color", function(d) { return z[a](d[a]); })
        .attr("stop-opacity", 1);

    gradient.append("svg:stop")
        .attr("offset", "100%")
        .attr("stop-color", function(d) { return z[b](d[b]); })
        .attr("stop-opacity", 1);

    path
        .style("stroke", function(d) { return "url(#gradient-" + a + "-" + d.index + ")"; })
        .attr("d", function(d) {
          return "M" + x(a) + "," + y(d[a + "-offset"])
              + "h" + x.rangeBand()
              + curve(a, b, d)
              + "h1";
        });
  });

  bump.append("path")
      .style("stroke-width", function(d) { return y(d.weight); })
      .style("stroke", function(d) { return z.c(d.c); })
      .attr("d", function(d) { return "M" + x("c") + "," + y(d["c-offset"]) + "h" + x.rangeBand(); });
});

function type(d, i) {
  for (var k in d) d[k] = +d[k];
  d.index = i;
  return d;
}

function line(d) {
  var path = [];
  x.domain().slice(1).forEach(function(b, i) {
    var a = x.domain()[i];
    path.push("L", x(a), ",", y(d[a + "-offset"]), "h", x.rangeBand(), curve(a, b, d));
  });
  path[0] = "M";
  path.push("h", x.rangeBand());
  return path.join("");
}

function curve(a, b, d) {
  return "C" + (x(a) + xTangent + x.rangeBand()) + "," + y(d[a + "-offset"]) + " "
      + (x(b) - xTangent) + "," + y(d[b + "-offset"]) + " "
      + x(b) + "," + y(d[b + "-offset"]);
}

</script>

data.tsv

weight	a	b	c
158	12.58	8.15	20.13
147	6.25	-5.08	4.31
129	5.71	6.68	6.28
126	25.38	23.75	18.83
97	14.29	12.8	11.13
95	0.39	-5.85	-1.57
95	10.34	7.32	0.1
90	10.3	8.9	13.96
73	23.77	25.43	24.73
73	8.26	6.52	8.91
68	11.84	8.97	14.2
54	5.99	5.42	11.37
52	30.81	30.22	30.41
49	21.36	24.25	24.86
47	17.21	14.87	13.9
38	26.77	23.35	30.25
31	23	25.08	16.4
31	5.79	13.94	9.67
29	3.96	-10.45	0.09
27	27.18	31.83	15.14
26	19.1	16.53	15.94
23	18.66	22.12	24.1
20	4.03	7.69	8.05
16	21.87	22.08	17.92
15	27.49	36.58	25.15
14	21.55	18.41	20.25
12	23.27	23.55	20.4
12	0.47	-2.25	0.42
11	23.39	21.7	19.01
10	9.92	4.51	11.14