Saturday, April 23, 2016

Smooth color transition between multiple color stops

Leave a Comment

I am trying to draw Scroll reach map of my website using canvas. I have bunch Y coordinates and the number of visitors who reached the point. I am coloring each coordinates using the number of visitors who reached the point. Let's say

10 users are visiting my website.

5 users scrolling upto = 0, 300px 3 users scrolling upto = 300, 700px 2 users scrolling upto = 700, 800px

Now, I have 3 color stops (300, 700, 800)px respectively. And the coloring should be based on the number of users. I did this but the transition between the stops are smooth, it looks solid.

var Scroll = function(config, data) {     var container, computed;     this.data = data;     this.config = config;     container = document.querySelector(this.config.container);     this.canvas = document.createElement('canvas');     this.ctx = this.canvas.getContext('2d');     computed = getComputedStyle(container) || {};     this.canvas.className = 'zarget-scrollmap-canvas';     this.width = this.canvas.width = this.config.width || +(computed.width.replace(/px/, ''));     this.height = this.canvas.height = this.config.height || +(computed.height.replace(/px/, ''));     this.canvas.style.cssText = 'position:absolute; top: 0px; left:0px';     container.appendChild(this.canvas);      var map = function(value, istart, istop, ostart, ostop) {        return ostart + (ostop - ostart) * ((value - istart) / (istop - istart));     };      this.mapIntensityToColor = function(intensity, min, max) {        var cint = map(intensity, min, max, 0, 255);        /**        * Based On Rainbow Gradient        */        if (cint > 204) {           return [255, Math.round(map(intensity, min, max, 255, 0)), 0];        }         if (cint > 153) {           max = (203 / 255 * 100) * (max / 100);           return [Math.round(map(intensity, 0, max, 255, 0)), 255, 0];        }         if (cint > 102) {           max = (153 / 255 * 100) * (max / 100);           return [0, 255, Math.round(map(intensity, 0, max, 255, 0))];        }         if (cint > 0) {           max = (102 / 255 * 100) * (max / 100);           return [0, Math.round(map(intensity, 0, max, 255, 0)), 255];        }        max = (51 / 255 * 100) * (max / 100);        return [0, 0, Math.round(map(intensity, 0, max, 0, 255))];     };      this.draw = function(data) {       var min = 0;       var max = 1300;       var data = [         [0, 50, 1300],         [50, 100, 1100],         [100, 150, 1100],         [150, 200, 1000],         [200, 250, 500],         [250, 300, 400],         [300, 350, 300],         [350, 450, 200],         [450, 500, 400],         [500, 900, 0],         [900, 950, 300],         [950, 3350, 0]       ];       var point, startY, endY, alpha, val, color;       this.ctx.globalAlpha = 0.75;        for (var i = 0, l = data.length; i < l; i++) {         point = data[i];         startY = point[0];         endY = point[1];         val = point[2];         color = this.mapIntensityToColor(val, min, max);         this.ctx.fillStyle = "rgb(" + color.join(",") + ")";         this.ctx.fillRect(0, startY, this.width, endY - startY);     }  };}; var a = new Scroll({"container": "#overlay"}); a.draw(); 
#overlay {   width: 100%;   height: 2000px;   position: absolute;   top: 0px;   left: 0px; } 
<div id="overlay"></div> 

Output of above function

enter image description here

Expected Output

enter image description here

I tried using Linear gradient but the color blending is very poor.

Linear Gradient

https://jsfiddle.net/bdxeca48/2/

2 Answers

Answers 1

You could just use a linear gradient with a single color stop at the "middle" of each step:

this.draw = function(data) {     var min = 0;     var max = 1300;     var data = [         [0, 50, 1300],         [50, 100, 1100],         [100, 150, 1100],         [150, 200, 1000],         [200, 250, 500],         [250, 300, 400],         [300, 350, 300],         [350, 450, 200],         [450, 500, 400],         [500, 900, 0],         [900, 950, 300],         [950, 3350, 0]     ];      var point, startY, endY, alpha, val, color;     this.ctx.globalAlpha = 0.75;      // Calculate the maximum y scrolled     var maxScrollY = 0;     for(var i = 0, l = data.length; i < l; i++){         point = data[i];         if(maxScrollY < point[1]){             maxScrollY = point[1];         }     }     // here, maxScrollY will be 3350      var linearGrad = ctx.createLinearGradient(0, 0, 0, maxScrollY);      // Build the gradient     for (var i = 0, l = data.length; i < l; i++) {         point = data[i];          // You have to divide the coordinate by maxScrollY to get         // coordinates between 0.0 and 1.0         startY = point[0] / maxScrollY;         endY = point[1] / maxScrollY;          // Take the y coordinate between startY and endY for the color stop coordinate         var middleY = startY + (endY-startY)/2;          val = point[2];         color = this.mapIntensityToColor(val, min, max);         linearGrad.addColorStop(middleY, "rgb(" + color.join(",") + ")");     }      // Then just render     ctx.fillStyle = linearGrad;     ctx.fillRect(0, 0, this.width, maxScrollY); } 

Result: Result

Here is a demo: https://jsfiddle.net/4n294hb1/

Answers 2

Fiddle here: https://jsfiddle.net/Lfhdzw2c/

I replaced your IntensityToColor function with this, which I borrowed from https://rainbowcoding.com/how-to-create-rainbow-text-in-html-css-javascript/

function color_from_hue(hue) {   var h = hue/60;   var c = 255;   var x = (1 - Math.abs(h%2 - 1))*255;   var color;    var i = Math.floor(h);   if (i == 0) color = rgb_to_hex(c, x, 0);   else if (i == 1) color = rgb_to_hex(x, c, 0);   else if (i == 2) color = rgb_to_hex(0, c, x);   else if (i == 3) color = rgb_to_hex(0, x, c);   else if (i == 4) color = rgb_to_hex(x, 0, c);   else color = rgb_to_hex(c, 0, x);    return color; } 
If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment