Search code examples
javascriptd3.jslegendsamplinglogarithm

How to use logarithm sampling if data are between 0 and 1 to create legend using D3


I am creating an continuous legend(have created using linear gradient) using D3 for linear sampling. Now I have to use same legend for logarithm sampling. But my data are between 0 and 1 as shown below. I see that domain values for logarithm sampling cannot be zero(gives infinite) and for values in zero range suppose (0.1, 0.2 and so on) gives negative values. How can I use my data to create continuous legend using logarithm sampling.

My Data:

[
[0.0, 255, 0, 0],
[0.2, 255, 255, 0],
[0.4, 0, 255, 0],
[0.6, 0, 255, 255],
[0.8, 0, 0, 255],
[1.0, 255, 0, 255]
] 

where 0.0, 0.2,0.4 and so on is the domain value and rest are rgb values for the point.

const colorScale = scaleLog().domain([min,max]); // in my case min and max are [0, 1]
const id = "linear-gradient-" + id + "0";
linearGradient = defs
.append("linearGradient")
.attr("id", id)
.attr("x1", "0%")
.attr("x2", horizontal ? "100%" : "0%")
.attr("y1", "0%")
.attr("y2", horizontal ? "0%" : "100%");

// append the color
linearGradient
.selectAll("stop")
.data(itemColor)
.enter()
.append("stop")
.attr("offset", function (data) {
return data.offset + "%";
})
.attr("stop-color", function (data) {
return data.color;
});

// draw the rectangle and fill with gradient
svgLegend
.append("rect")
.attr("x", 35)
.attr("y", horizontal ? 70 : 18)
.attr("width", horizontal ? "189" : 20)
.attr("height", horizontal ? 20 : "149")
.style("fill", "url(#" + currentIndex + ")");

// create tick
const horizontalAxisLeg = axisBottom(scaleLog().domain([min, max]).tickValues(colorScale.domain());

My legend looks something like this : https://jsfiddle.net/z7gn8p5t/


Solution

  • Except for using d3.scaleSymlog instead of d3.scaleLog (have a look here), there's no issue in using a log scale.

    Here is your code with some changes, on the top you have the log scale, and below it I put a linear scale for comparison:

    const itemColor = [{
        offset: 0.0,
        color: "#ff0000"
      },
      {
        offset: 0.2,
        color: "#ffff00"
      },
      {
        offset: 0.4,
        color: "#00ff00"
      },
      {
        offset: 0.6,
        color: "#00ffff"
      },
      {
        offset: 0.8,
        color: "#0000ff"
      },
      {
        offset: 1.0,
        color: "#ff00ff"
      }
    ];
    
    const svg = d3.select("svg");
    const colorScale = d3.scaleSymlog().domain([0, 1]).range([0, 400]); // in my case min and max are [0, 1]
    const colorScale2 = d3.scaleLinear().domain([0, 1]).range([0, 400]); // in my case min and max are [0, 1]
    const id = "linear-gradient-0";
    const linearGradient = svg.append("defs")
      .append("linearGradient")
      .attr("id", id)
      .attr("x1", "0%")
      .attr("x2", "100%")
      .attr("y1", "0%")
      .attr("y2", "0%");
    
    // append the color
    linearGradient
      .selectAll("stop")
      .data(itemColor)
      .enter()
      .append("stop")
      .attr("offset", function(data) {
        return colorScale(data.offset) / 4 + "%";
      })
      .attr("stop-color", function(data) {
        return data.color;
      });
    
    const linearGradient2 = svg.append("defs")
      .append("linearGradient")
      .attr("id", "linear-gradient-1")
      .attr("x1", "0%")
      .attr("x2", "100%")
      .attr("y1", "0%")
      .attr("y2", "0%");
    
    // append the color
    linearGradient2
      .selectAll("stop")
      .data(itemColor)
      .enter()
      .append("stop")
      .attr("offset", function(data) {
        return colorScale2(data.offset) / 4 + "%";
      })
      .attr("stop-color", function(data) {
        return data.color;
      });
    
    // draw the rectangle and fill with gradient
    svg.append("rect")
      .attr("x", 10)
      .attr("y", 18)
      .attr("width", 400)
      .attr("height", 20)
      .style("fill", "url(#linear-gradient-0)");
    
    // create tick
    svg.append("g").attr("transform", "translate(10,45)").call(d3.axisBottom(colorScale));
    
    // draw the rectangle and fill with gradient
    svg.append("rect")
      .attr("x", 10)
      .attr("y", 88)
      .attr("width", 400)
      .attr("height", 20)
      .style("fill", "url(#linear-gradient-1)");
    
    // create tick
    svg.append("g").attr("transform", "translate(10,115)").call(d3.axisBottom(colorScale2));
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.4.0/d3.min.js"></script>
    <svg width="500"></svg>