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)