DEV Community

Lionel Marco
Lionel Marco

Posted on

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

Top comments (0)