Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Color ramps? #41

Closed
mbostock opened this issue May 16, 2017 · 12 comments
Closed

Color ramps? #41

mbostock opened this issue May 16, 2017 · 12 comments

Comments

@mbostock
Copy link
Member

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;
};
@mbostock
Copy link
Member Author

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

@mbostock
Copy link
Member Author

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?

@mbostock
Copy link
Member Author

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]));

@tomshanley
Copy link

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

@mbostock
Copy link
Member Author

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.

@tomshanley
Copy link

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.

@syntagmatic
Copy link

The visual legend is the color scale. #teamramp

@syntagmatic
Copy link

syntagmatic commented May 18, 2017

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']))

@aendra-rininsland
Copy link

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)?

@mbostock
Copy link
Member Author

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

@mbostock
Copy link
Member Author

https://observablehq.com/@d3/color-legend

@curran
Copy link
Contributor

curran commented Feb 24, 2020

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

5 participants