DEV Community

Lionel Marco
Lionel Marco

Posted on

3 2

React Color Scale Interpolation

Sometimes we have to work with colors, either for labels, icons, bar charts or for a choropleth maps.

It will show a way in which given two colors, the color between them will be calculated, or a scale with many colors will be generated.

For UI components will be use MATERIAL-UI library.

Image

Table of Contents

1) Color interpolation

Here the more important function is which, given two colors, them
calculate a resulting color. It will be more similar to one or the other,
acording to a conversion ratio, that will vary between 0 and 1.




  const interpolate = (start,end,ratio)=>{    
    const r = Math.trunc(ratio*end[0] + (1-ratio)*start[0])
    const g = Math.trunc(ratio*end[1] + (1-ratio)*start[1])
    const b = Math.trunc(ratio*end[2] + (1-ratio)*start[2])    
    return [r,g,b]
  }



Enter fullscreen mode Exit fullscreen mode

For example, interpolating in the middle of Black and White:

interpolate([0,0,0],[255,255,255],0.5)

We get Gray:

[127,127,127]

2) Color Selector

The MATERIAL-UI library have a Select control, in this case we use to display a list of color and choice one of them.

There are two color selector, one for start and other for end.



          <Select  value={color1}                                    
                   name={'color1'}
                   onChange={ this.onColorChange}
                   renderValue={ showIcon } 
                 >                    
                     { colors.map( e =>  <MenuItem  key={e} value={e}><ColorIcon color={e} /></MenuItem> )}    
          </Select> 


Enter fullscreen mode Exit fullscreen mode

The colors come from a previously declared array:



const colors = ['#800080','#FF0000','#FFD700','#00FF00','#006400','#0000FF']; //purple,red,gold,darkgreen,blue


Enter fullscreen mode Exit fullscreen mode

Color Icon

On every choice of our select an icon with their respective color is displayed.
The icon receive a props 'color', basically it is a rectangule filled with the given color.



function ColorIcon({color}) {
 return (
 <SvgIcon   viewBox="0 0 50 20" style={{ width: 50, height:20  }}>     
   <rect   fill={color} x={0} y='0' width={50} height={20}  ></rect>            
 </SvgIcon>
);
};


Enter fullscreen mode Exit fullscreen mode

Show Icon

The select have a 'renderValue' props, wich give the flexibility of show another thing that only text.


 showIcon } 

Enter fullscreen mode Exit fullscreen mode


function showIcon(value) {
  return (  <ColorIcon color={value}/> );
}



Enter fullscreen mode Exit fullscreen mode

3) Ratio Slider

The MATERIAL-UI library have a Slider control, the slider will control the ratio of color mix. Varying from 0 to 100, then will be re maped to 0-1.



          <Slider
            value={ratio}          
            onChange={ this.onRatioChange}
            step={10} min={0} max={100}
            valueLabelDisplay="auto"
            marks={[ {value: 0,label: '0%'}, {value: 50,label: '50%'}, {value: 100,label: '100%'}]}                                   
          />



Enter fullscreen mode Exit fullscreen mode

Every change on the slider will update the state and trigger a new render:



  onRatioChange = (event, newValue) => {    
    this.setState(prevState => ({...prevState,ratio: newValue})); 
  };


Enter fullscreen mode Exit fullscreen mode

4) Full code

Next the full code is showed, imports are ommited just for shortness.



function ColorIcon({color}) {
 return (
 <SvgIcon   viewBox="0 0 50 20" style={{ width: 50, height:20  }}>     
   <rect   fill={color} x={0} y='0' width={50} height={20}  ></rect>            
 </SvgIcon>
);
};


function showIcon(value) {
  return (  <ColorIcon color={value}/> );
}

const colors = ['#800080','#FF0000','#FFD700','#00FF00','#006400','#0000FF']; //purple,red,gold,darkgreen,blue

export default class ColorInterpolation extends React.Component {

  constructor(props) {
    super(props);
    this.state = {color1:colors[2],color2:colors[1],ratio:50}      
  };   

  onColorChange = (e) => {    
    const {name,value} = e.target;
    this.setState(prevState => ({...prevState,[name]: value}));  
  };

  onRatioChange = (event, newValue) => {    
    this.setState(prevState => ({...prevState,ratio: newValue})); 
  };  

  render() {   
    //console.log("Render");     
    const {color1,color2,ratio} = this.state;    
    const interpolatedColor=getColor(color1,color2,ratio);

    return (    

      <div style={{  
        maxWidth:'500px',         
        display: "flex" ,                    
        flexDirection: "column", 
        margin:'10px',padding:'20px',
        border: '2px solid grey',
        borderRadius:'4px'
        }}>

        <div style={{               
           display: "flex" ,                    
           flexDirection: "row",      
           alignItems: "center",
           justifyContent: "space-around" }}>

          <Typography> Source: </Typography> 
          <Select  value={color1}                                    
                   name={'color1'}
                   onChange={ this.onColorChange}
                   renderValue={ showIcon } >                    
                     { colors.map( e =>  <MenuItem  key={e} value={e}><ColorIcon color={e} /></MenuItem> )}    
          </Select>    
          <Typography> Target: </Typography>       
          <Select   value={color2}                                    
                    name={'color2'}
                    onChange={ this.onColorChange}
                    renderValue={ showIcon } >                    
                     { colors.map( e =>  <MenuItem  key={e} value={e}><ColorIcon color={e} /></MenuItem> )}
          </Select>    
          </div>  
          <Slider
            value={ratio}          
            onChange={ this.onRatioChange}
            step={10} min={0} max={100}
            valueLabelDisplay="auto"
            marks={[ {value: 0,label: '0%'}, {value: 50,label: '50%'}, {value: 100,label: '100%'}]}
          />
          <div style={{marginTop:'5px', display: "flex" , alignItems: "center", justifyContent: "center" }}>   
            <Typography > Interpolated:</Typography>        
            <Typography > {ratio}% </Typography>        
            <ColorIcon color={interpolatedColor} />
            <Typography >{interpolatedColor}</Typography>
          </div>    
          <div style={{marginTop:'5px', display: "flex" , alignItems: "center", justifyContent: "center" }}>   
            <Typography > Scale:</Typography>        
            { [0,10,20,40,60,80,100].map( (v,i)=> {
              let c =getColor(this.state.color1,this.state.color2,v)
            return <ColorIcon key={i} color={c} />})}
          </div> 
        </div>
    );
}
}

function getColor(c1,c2,ratio)
{

  const interpolate = (start,end,ratio)=>{    
    const r = Math.trunc(ratio*end[0] + (1-ratio)*start[0])
    const g = Math.trunc(ratio*end[1] + (1-ratio)*start[1])
    const b = Math.trunc(ratio*end[2] + (1-ratio)*start[2])    
    return [r,g,b]
  }

  const hexToRgb = (hex) =>   
     [ 
       parseInt(hex.substr(1,2),16),
       parseInt(hex.substr(3,2),16),
       parseInt(hex.substr(5,2),16),
     ];  

  const rgbToHex = (rgb) => '#' + rgb.map(x => {
    const hex = x.toString(16)
    return hex.length === 1 ? '0' + hex : hex
  }).join('');

  const rgbInterpolated = interpolate(hexToRgb(c1),hexToRgb(c2),ratio/100);
  return rgbToHex(rgbInterpolated);
}



Enter fullscreen mode Exit fullscreen mode

Image of Datadog

The Future of AI, LLMs, and Observability on Google Cloud

Datadog sat down with Google’s Director of AI to discuss the current and future states of AI, ML, and LLMs on Google Cloud. Discover 7 key insights for technical leaders, covering everything from upskilling teams to observability best practices

Learn More

Top comments (0)

Image of Datadog

Create and maintain end-to-end frontend tests

Learn best practices on creating frontend tests, testing on-premise apps, integrating tests into your CI/CD pipeline, and using Datadog’s testing tunnel.

Download The Guide

👋 Kindness is contagious

Explore a sea of insights with this enlightening post, highly esteemed within the nurturing DEV Community. Coders of all stripes are invited to participate and contribute to our shared knowledge.

Expressing gratitude with a simple "thank you" can make a big impact. Leave your thanks in the comments!

On DEV, exchanging ideas smooths our way and strengthens our community bonds. Found this useful? A quick note of thanks to the author can mean a lot.

Okay