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.
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]
}
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>
The colors come from a previously declared array:
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.
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.
showIcon }
function showIcon(value) {
return ( <ColorIcon color={value}/> );
}
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%'}]}
/>
Every change on the slider will update the state and trigger a new render:
onRatioChange = (event, newValue) => {
this.setState(prevState => ({...prevState,ratio: newValue}));
};
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);
}
Top comments (0)