1

I am trying to create like a time countdown using canvas arc, the drawing is ok, but the arc will have a stroke color, which I want to sort of blend.

The way I draw the arc is by using a number between 0 to 100, 100 is basically the full arc, 0 is nothing. When the value is over 66 and under (or equal to) 100 it'll be a blend between green and orange, 100 is total green and 66 is total orange. 65 and down will be a mix between orange and red, where total red is 0.

I am clueless on where to start, no ideas on what to search for to do research on how to get the values.

Can anybody please point me in the right direction?

Here's my code so far x)

static color (value)
{
    const StartColor  = 0x66BB6A; // Green
    const MiddleColor = 0xFFA726; // Orange
    const EndColor    = 0xEF5350; // Red  
}

EDIT: This is the result I was looking for. It's now working exactly like I want it to. Cheers.

https://youtu.be/IeR_zMzSaAU

iHack2712
  • 41
  • 1
  • 7
  • You can use RGB colors with a 0 - 100 notation. Example rgb(0, 100, 0) gives green. [fiddle](https://jsfiddle.net/peterbode1989/v3nqxs9o/) – Peter Bode Dec 18 '18 at 22:46
  • @PeterBode I know I could do that, although the problem now is I have 3 different colors with both red, green and blue values, how would it now the correct values for each percent? – iHack2712 Dec 18 '18 at 22:48
  • @PeterBode That was exactly how I started it to test the arc, although the issue here is that I need a nice blend between the colors in my code snippet, not from black to green. now it's one color to another, then another, based on percentage. Whereas all colors have both red, green and blue values. – iHack2712 Dec 18 '18 at 23:05
  • Possible duplicate of [Anyway possible to make a countdown timer change color at an certain time with javascript?](https://stackoverflow.com/questions/31298941/anyway-possible-to-make-a-countdown-timer-change-color-at-an-certain-time-with-j) – Tedinoz Dec 18 '18 at 23:05
  • @Tedinoz I don't see how my question is a duplicate, as I need to find a correct blend between colors, not switch the color when a value is lower or higher than. Can you please explain how my question is the same as the one you linked? Thanks :) – iHack2712 Dec 18 '18 at 23:10
  • From my perspective the issue is about transitioning between colours over a period of time. The question I referred to addresses the aspect of change in colour. – Tedinoz Dec 19 '18 at 00:45
  • Have you looked at this Q&A (https://stackoverflow.com/questions/14819058/mixing-two-colors-naturally-in-javascript)? – Tedinoz Dec 19 '18 at 00:50
  • @Tedinoz My question is about blending colors at a certain strength – iHack2712 Dec 19 '18 at 01:25
  • Perhaps you should reference a site where you have seen an example of this, or attach a screenshot of what outcome you are trying to achieve. – Tedinoz Dec 19 '18 at 04:12
  • I know I come very late to the party, but to let you know I've edited [my original answer](https://stackoverflow.com/a/53842291/220272), you may find it useful since it does exactly what you show on the video provided. – a--m Jan 02 '19 at 00:19
  • @RichardSandberg I’m pleased you’ve got it working as you wanted. Would you clarify, for the benefit of posterity, which of the answers you implemented? And maybe even explain your code. I looked at the YouTube video, but couldn’t see the link between it and the goal you began with. – Tedinoz Jan 02 '19 at 00:29

4 Answers4

1

Note: This answer is a late and big edit from the original one. After a first attempt were I missunderstood the OP intentions, the OP added a link with an example, since then I had the intention to provide a solution since I wasn't satisfied with the current answers and the exercice was fun to build.


The breakdown of the answer goes into two main parts:

  1. Blend the colors (there are already answers that tackle this, using a library or create a method to interpolate/blend the color hex value)
  2. Draw an arc and use the blended color

/*
 * blending colors 
 * (borrowed and modified from @mark-meyer answer)
 */

// break hex integer into components:
const getComponents = (color) => Array.from({length: 3}, (_, i) => Math.floor(color / 16 ** (2 * i) % 16 ** 2))

// interpolate arrays by component
const interpolate = (arr1, arr2, percent) => arr1.map((v, index) => Math.floor(v + (arr2[index] - v) * (percent / 100)))

const colorBlend = value => {
  const StartColor  = 0x11FF11; // Green
  const MiddleColor = 0xFFA726; // Orange
  const EndColor    = 0xEF5350; // Red
  
  let [start, end, v] = value < 50 
      ? [StartColor, MiddleColor, value * 2 ] 
      : [MiddleColor, EndColor, (value - 50) * 2]

  let interpoled = interpolate(getComponents(start), getComponents(end), v)
  
  const color = interpoled
  .reduce((n, component, index) => n + component * (16 ** (index * 2)), 0)
  .toString(16).padStart(6, '0')
  return `#${color}`
}

/*
 * draw an arc
 */
const canvas = document.getElementById('canvas1')
const ctx = canvas.getContext('2d')
const size = 30
let p = 0

const grad = ctx.createLinearGradient(size, size, size * 2, size * 2)
grad.addColorStop(0, "#66BB6A")
grad.addColorStop(0.5, "#FFA726")
grad.addColorStop(1, "#EF5350")

ctx.lineWidth = 6

const draw = () => {
  p += 0.01
  ctx.clearRect(0, 0, canvas.width, canvas.height)
  ctx.strokeStyle = colorBlend(p * 50)
  ctx.beginPath()
  ctx.arc(size * 1.5, size * 1.5, size, 1.5 * Math.PI, (1.5 + p) * Math.PI)
  ctx.stroke()
  if (p < 2) {
    window.requestAnimationFrame(draw);
  }
}

window.requestAnimationFrame(draw);
<canvas id="canvas1" width="500" height="500"></canvas>
a--m
  • 4,716
  • 1
  • 39
  • 59
  • I had no idea that was even a thing! Thanks! Although is it possible to use the same *current* color for the entire arc? Cheers! – iHack2712 Dec 18 '18 at 22:54
  • @RichardSandberg could you please elaborate on "Although is it possible to use the same current color for the entire arc?". I don't understand what do you mean... – a--m Dec 18 '18 at 22:59
  • So I have a value (percentage 100-0), so if the value is 50, it's a half arc, that half arc will have the orange color, if the value is 66 it will be a 50/50 mix of the green and orange color, if the value is 33 it's a 50/50 mix of orange and red, if the value is 0 it's the red color. But I'd like to have it so it finds a nice blend between the colors, so say e.g. the value is 90, the arc color should then be more green than orange – iHack2712 Dec 18 '18 at 23:02
  • @a--m Have you tried to turn this into a countdown timer? I realise that is works perfectly as a static display, but does it translate into a progressive display? – Tedinoz Dec 19 '18 at 05:57
  • @RichardSandberg Original was good, progressive display is a major step forward, AND it includes the code, so others can use and adapt. – Tedinoz Jan 02 '19 at 00:35
1

Here is a quick fiddle I made.

var c = document.getElementById("foo");
var i = 0;
var timer = setInterval(function() {

    var ctx = c.getContext("2d");
    ctx.beginPath();
    ctx.arc(75, 75, 50, 0, (i / 100) * Math.PI);
    ctx.strokeStyle = 'RGB('+((100-i)*2)+', '+i+', 0)';
    ctx.stroke();

    if((i / 100) == 2) {
        clearInterval(timer);
        console.log('FULL');
    }
    i++;
}, 100);
Peter Bode
  • 222
  • 1
  • 9
  • I like this because it uses a countdown as an example. Am I right, it starts off as dark green and then progressively transitions to bright green, but the entire arc changes colour, not just the 'new' part of the arc? The OP, as I understand it, is looking for the existing portions of the arc to retain their colour. – Tedinoz Dec 19 '18 at 04:44
  • @Tedinoz yeah, the amount of rotation is also calculating the value. Was playing around with it, and you can get pretty awesome color variations. Adding ((100-i)*2) as the R-value makes it blend from red to green. – Peter Bode Dec 19 '18 at 07:56
1

Of course, using a library dedicated to this will be easier. But just in case anyone is interested in doing the math, you can break the hex integers into components and then interpolate each component separately to get the color at a specific place:

// break hex integer into components:
const getComonents = (color) => Array.from({length: 3}, (_, i) => Math.floor(color / 16 ** (2 * i) % 16 ** 2))

// interpolate arrays by component
const interpolate = (arr1, arr2, percent) => arr1.map((v, index) => Math.floor(v + (arr2[index] - v) * (percent / 100)))

function colors(value) {
  const StartColor  = 0x11FF11; // Green
  const MiddleColor = 0xFFA726; // Orange
  const EndColor    = 0xEF5350; // Red
  
  let [start, end, v] = value < 50 
      ? [StartColor, MiddleColor, value * 2 ] 
      : [MiddleColor, EndColor, (value - 50) * 2]

  let interpoled = interpolate(getComonents(start), getComonents(end), v)
  
  return interpoled.reduce((n, component, index) => n + component * (16 ** (index * 2)), 0)
}

const canvas = document.getElementById('the_canvas')
const ctx = canvas.getContext('2d')

for (let i = 0; i<100; i++){
  ctx.beginPath();
  
  // padStart is needed to make sure a value of `ff` becomes `0000ff`
  ctx.fillStyle = "#" + colors(i).toString(16).padStart(6, '0')
  ctx.rect(i*5, 0, i*5+5, 300);
  ctx.fill()
}
<canvas id="the_canvas" width="500" height="300"></canvas>
Mark
  • 90,562
  • 7
  • 108
  • 148
0

I have assumed that you want to

  • create a color scale based on three known colors - '66BB6A', 'FFA726', 'EF5350'
  • input a number from within a domain (0 - 100) and get a new color calculated from the scale.

Chroma.js is a nice little js library that can make color scales and conversions.

const colorScale = chroma.scale(['66BB6A', 'FFA726', 'EF5350']).domain([0,100]);

console.log(colorScale(20).toString())

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.fillStyle = colorScale(20);
ctx.beginPath();
ctx.arc(100, 75, 50, 0, 2 * Math.PI);
ctx.fill();
<script src="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/2.0.2/chroma.min.js"></script>
<canvas id="myCanvas"></canvas>
ksav
  • 20,015
  • 6
  • 46
  • 66