d3 / d3-axis

Human-readable reference marks for scales.

Home Page:https://d3js.org/d3-axis

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Color ramps?

mbostock opened this issue · comments

d3.rampHorizontal = function(x, color) {
  var size = 16;

  function ramp(g) {
    var image = g.selectAll("image").data([null]),
        xz = x.range(),
        x0 = xz[0],
        x1 = xz[xz.length - 1],
        canvas = document.createElement("canvas"),
        context = (canvas.width = x1 - x0 + 1, canvas.height = 1, canvas).getContext("2d");

    for (var i = x0; i <= x1; ++i) {
      context.fillStyle = color(x.invert(i));
      context.fillRect(i - x0, 0, 1, 1);
    }

    image = image.enter().append("image").merge(image)
        .attr("x", x0)
        .attr("y", -size)
        .attr("width", x1 - x0 + 1)
        .attr("height", size)
        .attr("preserveAspectRatio", "none")
        .attr("xlink:href", canvas.toDataURL());
  }

  ramp.position = function(_) {
    return arguments.length ? (x = _, ramp) : x;
  };

  ramp.color = function(_) {
    return arguments.length ? (color = _, ramp) : color;
  };

  ramp.size = function(_) {
    return arguments.length ? (size = +_, ramp) : size;
  };

  return ramp;
};

Example usage:

var color = d3.scaleSequential(d3.interpolateRainbow);

var x = d3.scaleLinear().range([margin.left, width - margin.right]);

svg.append("g")
    .attr("transform", `translate(0,${height - margin.bottom})`)
    .call(d3.rampHorizontal(x, color))
    .call(d3.axisBottom(x));

Example output:

screen shot 2017-05-16 at 3 20 33 pm

Questions:

  1. Is “ramp” the right name? A swatch? A gradient? An image? A continuous color key? A sequential color key?

  2. Do we need separate bottom- and top-orientations? What if you want to use d3.axisTop and have the ramp hang below the axis? (Likewise for vertical orientation.)

  3. Is it helpful to have this primitive separate from the axis, or should we just combine the two? If we combined them, we could provide a default position scale with range [0, 320]… though unsure how useful that default would be.

  4. Or should we go the other way, and just have a helper for creating the canvas data URL?

If we combined them:

var color = d3.scaleSequential(d3.interpolateRainbow);

svg.append("g")
    .attr("transform", `translate(0,${height - margin.bottom})`)
    .call(d3.rampHorizontal(color).range([margin.left, width - margin.right]));

My thoughts..

  1. Out of those options, "Ramp" makes most sense to me in this situation, and is used in other applications I use
  2. Yes, I would think so. I would make sense to me to have similar options to axis (top/bottom/left/right)
  3. Separate
  4. Don't know

We can’t internalize the position scale inside the ramp component if we wish to allow non-linear scales, which coincidentally is the motivation for this request (a key for a continuous, diverging log color scale). Also if we internalized the scale, we’d have to re-expose all the standard axis properties, such as for setting the tick values and tick format. That’s not too onerous I suppose but it would be nice to avoid.

I think it’s still worth considering whether we want to pare down the abstraction a little further so that it just generates the data URL; that way it can be used either to create an SVG image element or an HTML img element. On the other hand there are still quite a few tedious steps required to create the SVG image element even given this data URL, and it feels reasonable to make it SVG-specific since it’s designed to work in conjunction with an axis.

So… I think my answers are: (2) yes, four orientations; (3) separate, as originally proposed; (4) no.

I’m not sure the name “ramp” is right because normally I think of that as referring to the color scale, rather than the key (or “guide” or “legend”) for said scale.

I think key/legend/guide might imply that I could use ordinal colour scales, or sequential scales with bands. But I take your point re Ramp.

commented

The visual legend is the color scale. #teamramp

commented

Here's a tricky nit. Can we tween the canvas so transitions magically work?

var ramp = function(scale) {
  d3.rampHorizontal(scale).range([left, right])
}

svg.append("g")
    .call(ramp(colorscale['RdBu']))
    .transition()
    .call(ramp(colorscale['PiYG']))

Is there any way this can be done with SVG gradients perhaps in addition to the Canvas-based approach? AFAIK, pushing an image element into SVG will break any workflow that necessarily requires using SVG Crowbar and importing the output into Illustrator (plus it admittedly just kinda feels yucky having rasters amongst a bunch of vector code)?

I use this often: https://observablehq.com/@mbostock/color-ramp

You can embed in SVG using canvas.toDataURL, e.g.: https://observablehq.com/@d3/hexbin-map

It's a nice implementation of color-legend, but it is possible to use outside of Observable?

If so, what license does it have? Thanks!