How to use SVG gradients to display varying colors relative to the size of the colored region

SwimsZoots picture SwimsZoots · Oct 10, 2012 · Viewed 13k times · Source

I'm using SVG and D3 to create bar graphs and have a question concerning how to color them. I've searched many questions on this site and others and haven't yet found anyone with the same issue.

I would like each bar to start at the bottom with one color, (yellow, e.g.) and, as the bar gets taller, progressively mix in more of the second color, (red, e.g.), so that the bars at their maximum potential height would be only the second color. In this example, the tops of the bars that are half their potential height would be orange.

I was able to write a function to produce, for a bar of any given height, a unique linear gradient that would color the bars as desired.

However, since this graph is dynamic and the heights of the bars may change many times per second as the data is refreshed, creating and applying a new gradient each time and for each bar is definitely not efficient and could result in serious lag in refreshing the bars. (I admit I haven't actually tried this with anything other than a static test case, so I could be wrong about that last assumption.)

Using a static gradient of course yields something like this, where the colors are mixed according to the height of the bar, not the height of the region:

Using static gradient

In my desired scenario, however, the smaller bars should have very little red or dark blue respectively.

My question, finally, is this: is there a way to

  1. create a single gradient that is applied to the SVG region itself (easy)
  2. have said gradient masked somehow (easy)
  3. then selectively unmasked underneath the rectangles representing the bars of the graph? (???)

Or, is there some other technique I'm overlooking?

Thanks

Answer

methodofaction picture methodofaction · Oct 11, 2012

This is simple to implement but a bit hard to grasp, you need to specify that the gradient units are userSpaceOnUse and then define the region where you want it to apply through x1, x2, y1, y2:

var gradient = svg
    .append("linearGradient")
    .attr("y1", minY)
    .attr("y2", maxY)
    .attr("x1", "0")
    .attr("x2", "0")
    .attr("id", "gradient")
    .attr("gradientUnits", "userSpaceOnUse")

gradient
    .append("stop")
    .attr("offset", "0")
    .attr("stop-color", "#ff0")

gradient
    .append("stop")
    .attr("offset", "0.5")
    .attr("stop-color", "#f00")

You can see a demo here: http://jsfiddle.net/ZCwrx/