React Color Scale Interpolation
Tags: react, ux
Published: 2021-10-11 23:40:52

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/09rx6gkws2dpkoo3ibdc.png)

## Table of Contents
* [1-Color interpolation](#interpolation)
* [2-Select](#select)
* [3-Slider](#slider)
* [4-Full code](#code)

## 1) Color interpolation <a name="interpolation"></a>

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.

```javascript

  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]
  }

```

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 <a name="select"></a>

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.

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

The colors come from a previously declared array:

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

### 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.

```javascript
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>
);
};
```

### Show Icon 

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

```renderValue={ showIcon } ```



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

```

## 3) Ratio Slider <a name="slider"></a>

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.

```javascript
          <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%'}]}                                   
          />

```

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


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

## 4) Full code <a name="code"></a>

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


```javascript
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);
}

```